Skip to content

Commit 3febac9

Browse files
committed
Pass all tests for DBLIB/TinyTDS connection mode and current state of rails 3.1.
1 parent 3dce4d4 commit 3febac9

21 files changed

+315
-188
lines changed

Gemfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,8 @@ group :development do
2626
gem 'ruby-prof', '0.9.1'
2727
gem 'ruby-debug', '0.10.3'
2828
end
29+
platforms :mri_19 do
30+
gem 'ruby-debug19', '0.11.6'
31+
end
2932
end
3033

RUNNING_UNIT_TESTS

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,6 @@ on a local branch of our remote tracking branch.
5959

6060
= Current Expected Failures
6161

62-
* test_count_explicit_columns(RelationTest) <0> expected but was <7>
63-
I believe this failure is actually incorrect for other DBs to be passing and
64-
will explore a patch to rails which disables ambiguous results sets.
65-
6662
* Misc Date/Time object instance matches when using ODBC mode.
6763

6864

Rakefile

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,13 @@ def test_libs(mode='odbc')
1111
end
1212

1313
def test_files
14-
Dir.glob("test/cases/**/*_test_sqlserver.rb").sort +
15-
(Dir.glob("#{ENV['RAILS_SOURCE']}/activerecord/test/cases/**/*_test.rb") -
16-
Dir.glob("#{ENV['RAILS_SOURCE']}/activerecord/test/cases/adapters/**/*_test.rb")).sort
14+
files = Dir.glob("test/cases/**/*_test_sqlserver.rb").sort
15+
unless ENV['ACTIVERECORD_UNITTEST_SKIP']
16+
ar_cases = Dir.glob("#{ENV['RAILS_SOURCE']}/activerecord/test/cases/**/*_test.rb")
17+
adapter_cases = Dir.glob("#{ENV['RAILS_SOURCE']}/activerecord/test/cases/adapters/**/*_test.rb")
18+
files << (ar_cases-adapter_cases).sort
19+
end
20+
files
1721
end
1822

1923

lib/active_record/connection_adapters/sqlserver/core_ext/active_record.rb

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ module ActiveRecord
1616
class_attribute :coerced_sqlserver_date_columns, :coerced_sqlserver_time_columns
1717
self.coerced_sqlserver_date_columns = Set.new
1818
self.coerced_sqlserver_time_columns = Set.new
19-
class << self
20-
alias_method_chain :reset_column_information, :sqlserver_cache_support
21-
end
2219
end
2320

2421
module ClassMethods
@@ -39,11 +36,6 @@ def coerce_sqlserver_time(*attributes)
3936
self.coerced_sqlserver_time_columns += attributes.map(&:to_s)
4037
end
4138

42-
def reset_column_information_with_sqlserver_cache_support
43-
connection.send(:initialize_sqlserver_caches) if connection.respond_to?(:sqlserver?)
44-
reset_column_information_without_sqlserver_cache_support
45-
end
46-
4739
end
4840

4941
end

lib/active_record/connection_adapters/sqlserver/database_statements.rb

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,39 @@ module ConnectionAdapters
33
module Sqlserver
44
module DatabaseStatements
55

6-
def select_one(sql, name = nil)
7-
result = raw_select sql, name, :fetch => :one
8-
(result && result.first.present?) ? result.first : nil
9-
end
10-
116
def select_rows(sql, name = nil)
12-
raw_select sql, name, :fetch => :rows
7+
raw_select sql, name, [], :fetch => :rows
138
end
149

15-
def execute(sql, name = nil, skip_logging = false)
10+
def execute(sql, name = nil)
1611
if id_insert_table_name = query_requires_identity_insert?(sql)
1712
with_identity_insert_enabled(id_insert_table_name) { do_execute(sql,name) }
1813
else
1914
do_execute(sql,name)
2015
end
2116
end
17+
18+
def exec_query(sql, name = 'SQL', binds = [])
19+
return raw_select(sql, name, binds, :ar_result => true) if binds.empty?
20+
if id_insert_table_name = query_requires_identity_insert?(sql)
21+
with_identity_insert_enabled(id_insert_table_name) { do_exec_query(sql, name, binds) }
22+
else
23+
do_exec_query(sql, name, binds)
24+
end
25+
end
26+
27+
def exec_insert(sql, name, binds)
28+
sql = "#{sql}; SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"
29+
exec_query(sql, name, binds)
30+
end
2231

2332
def outside_transaction?
2433
info_schema_query { select_value("SELECT @@TRANCOUNT") == 0 }
2534
end
35+
36+
def supports_statement_cache?
37+
true
38+
end
2639

2740
def begin_db_transaction
2841
do_execute "BEGIN TRANSACTION"
@@ -55,8 +68,8 @@ def empty_insert_statement_value
5568
"DEFAULT VALUES"
5669
end
5770

58-
def case_sensitive_equality_operator
59-
cs_equality_operator
71+
def case_sensitive_modifier(node)
72+
node.acts_like?(:string) ? Arel::Nodes::Bin.new(node) : node
6073
end
6174

6275
def limited_update_conditions(where_sql, quoted_table_name, quoted_primary_key)
@@ -190,8 +203,8 @@ def charset
190203

191204
protected
192205

193-
def select(sql, name = nil)
194-
raw_select sql, name, :fetch => :all
206+
def select(sql, name = nil, binds = [])
207+
exec_query(sql, name, binds).to_a
195208
end
196209

197210
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
@@ -230,6 +243,32 @@ def do_execute(sql, name = nil)
230243
end
231244
end
232245

246+
def do_exec_query(sql, name, binds)
247+
statement = quote(sql)
248+
names_and_types = []
249+
params = []
250+
binds.each_with_index do |(column,value),index|
251+
ar_column = column.is_a?(ActiveRecord::ConnectionAdapters::Column)
252+
next if ar_column && column.sql_type == 'timestamp'
253+
v = value
254+
names_and_types << if ar_column
255+
v = value.to_i if column.is_integer?
256+
"@#{index} #{column.sql_type_for_statement}"
257+
elsif column.acts_like?(:string)
258+
"@#{index} nvarchar(max)"
259+
elsif column.is_a?(Fixnum)
260+
v = value.to_i
261+
"@#{index} int"
262+
else
263+
raise "Unknown bind columns. We can account for this."
264+
end
265+
quoted_value = ar_column ? quote(v,column) : quote(v,nil)
266+
params << "@#{index} = #{quoted_value}"
267+
end
268+
sql = "EXEC sp_executesql #{statement}, #{quote(names_and_types.join(', '))}, #{params.join(', ')}"
269+
raw_select sql, name, binds, :ar_result => true
270+
end
271+
233272
def raw_connection_do(sql)
234273
case @connection_options[:mode]
235274
when :dblib
@@ -246,8 +285,8 @@ def raw_connection_do(sql)
246285

247286
# === SQLServer Specific (Selecting) ============================ #
248287

249-
def raw_select(sql, name=nil, options={})
250-
log(sql,name) do
288+
def raw_select(sql, name=nil, binds=[], options={})
289+
log(sql,name,binds) do
251290
begin
252291
handle = raw_connection_run(sql)
253292
handle_to_names_and_values(handle, options)
@@ -294,12 +333,13 @@ def handle_to_names_and_values(handle, options={})
294333
def handle_to_names_and_values_dblib(handle, options={})
295334
query_options = {}.tap do |qo|
296335
qo[:timezone] = ActiveRecord::Base.default_timezone || :utc
297-
qo[:first] = true if options[:fetch] == :one
298-
qo[:as] = options[:fetch] == :rows ? :array : :hash
336+
qo[:as] = (options[:ar_result] || options[:fetch] == :rows) ? :array : :hash
299337
end
300-
handle.each(query_options)
338+
results = handle.each(query_options)
339+
options[:ar_result] ? ActiveRecord::Result.new(handle.fields, results) : results
301340
end
302341

342+
# TODO [Rails31] Use options[:ar_result]
303343
def handle_to_names_and_values_odbc(handle, options={})
304344
@connection.use_utc = ActiveRecord::Base.default_timezone == :utc if @connection_supports_native_types
305345
case options[:fetch]
@@ -347,12 +387,12 @@ def handle_to_names_and_values_odbc(handle, options={})
347387
end
348388
end
349389

390+
# TODO [Rails31] Use options[:ar_result]
350391
def handle_to_names_and_values_adonet(handle, options={})
351392
if handle.has_rows
352393
names = []
353394
rows = []
354395
fields_named = options[:fetch] == :rows
355-
one_row_only = options[:fetch] == :one
356396
while handle.read
357397
row = []
358398
handle.visible_field_count.times do |row_index|
@@ -371,7 +411,6 @@ def handle_to_names_and_values_adonet(handle, options={})
371411
end
372412
row << value
373413
names << handle.get_name(row_index).to_s unless fields_named
374-
break if one_row_only
375414
end
376415
rows << row
377416
fields_named = true

lib/active_record/connection_adapters/sqlserver/query_cache.rb

Lines changed: 0 additions & 17 deletions
This file was deleted.

lib/active_record/connection_adapters/sqlserver/quoting.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ def quote(value, column = nil)
1515
else
1616
super
1717
end
18+
when nil
19+
column.respond_to?(:sql_type) && column.sql_type == 'timestamp' ? 'DEFAULT' : super
1820
else
1921
super
2022
end
@@ -32,6 +34,14 @@ def quote_column_name(name)
3234
def quote_table_name(name)
3335
quote_column_name(name)
3436
end
37+
38+
def substitute_at(column, index)
39+
if column.respond_to?(:sql_type) && column.sql_type == 'timestamp'
40+
nil
41+
else
42+
Arel.sql "@#{index}"
43+
end
44+
end
3545

3646
def quoted_true
3747
QUOTED_TRUE

lib/active_record/connection_adapters/sqlserver/schema_statements.rb

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ def indexes(table_name, name = nil)
3939

4040
def columns(table_name, name = nil)
4141
return [] if table_name.blank?
42-
cache_key = columns_cache_key(table_name)
43-
@sqlserver_columns_cache[cache_key] ||= column_definitions(table_name).collect do |ci|
42+
cache_key = unqualify_table_name(table_name)
43+
column_definitions(table_name).collect do |ci|
4444
sqlserver_options = ci.except(:name,:default_value,:type,:null).merge(:database_year=>database_year)
4545
SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
4646
end
@@ -198,11 +198,11 @@ def column_definitions(table_name)
198198
ELSE 1
199199
END as is_identity
200200
FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS columns
201-
WHERE columns.TABLE_NAME = '#{table_name}'
201+
WHERE columns.TABLE_NAME = @0
202202
AND columns.TABLE_SCHEMA = #{table_schema.nil? ? "schema_name() " : "'#{table_schema}' "}
203203
ORDER BY columns.ordinal_position
204204
}.gsub(/[ \t\r\n]+/,' ')
205-
results = info_schema_query { select(sql,nil) }
205+
results = info_schema_query { do_exec_query(sql, 'InfoSchema::ColumnDefinitions', [['table_name', table_name]]) }
206206
results.collect do |ci|
207207
ci = ci.symbolize_keys
208208
ci[:type] = case ci[:type]
@@ -351,12 +351,10 @@ def columns_cache_key(table_name)
351351

352352
def remove_sqlserver_columns_cache_for(table_name)
353353
cache_key = unqualify_table_name(table_name)
354-
@sqlserver_columns_cache[cache_key] = nil
355354
initialize_sqlserver_caches(false)
356355
end
357356

358357
def initialize_sqlserver_caches(reset_columns=true)
359-
@sqlserver_columns_cache = {} if reset_columns
360358
@sqlserver_views_cache = nil
361359
@sqlserver_view_information_cache = {}
362360
@sqlserver_quoted_column_and_table_names = {}
@@ -388,7 +386,7 @@ def with_identity_insert_enabled(table_name)
388386

389387
def set_identity_insert(table_name, enable = true)
390388
sql = "SET IDENTITY_INSERT #{table_name} #{enable ? 'ON' : 'OFF'}"
391-
do_execute(sql,'IDENTITY_INSERT')
389+
do_execute sql,'InfoSchema::SetIdentityInsert'
392390
rescue Exception => e
393391
raise ActiveRecordError, "IDENTITY_INSERT could not be turned #{enable ? 'ON' : 'OFF'} for table #{table_name}"
394392
end

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
require 'active_record/connection_adapters/sqlserver/database_limits'
66
require 'active_record/connection_adapters/sqlserver/database_statements'
77
require 'active_record/connection_adapters/sqlserver/errors'
8-
require 'active_record/connection_adapters/sqlserver/query_cache'
98
require 'active_record/connection_adapters/sqlserver/schema_statements'
109
require 'active_record/connection_adapters/sqlserver/quoting'
1110
require 'active_support/core_ext/kernel/requires'
@@ -61,7 +60,7 @@ def self.did_lose_sqlserver_connection(connection)
6160
module ConnectionAdapters
6261

6362
class SQLServerColumn < Column
64-
63+
6564
def initialize(name, default, sql_type = nil, null = true, sqlserver_options = {})
6665
@sqlserver_options = sqlserver_options.symbolize_keys
6766
super(name, default, sql_type, null)
@@ -87,6 +86,14 @@ def is_utf8?
8786
@sql_type =~ /nvarchar|ntext|nchar/i
8887
end
8988

89+
def is_integer?
90+
@sql_type =~ /int/i
91+
end
92+
93+
def sql_type_for_statement
94+
is_integer? ? sql_type.sub(/\(\d+\)/,'') : sql_type
95+
end
96+
9097
def default_function
9198
@sqlserver_options[:default_function]
9299
end
@@ -160,7 +167,6 @@ class SQLServerAdapter < AbstractAdapter
160167
include Sqlserver::DatabaseStatements
161168
include Sqlserver::SchemaStatements
162169
include Sqlserver::DatabaseLimits
163-
include Sqlserver::QueryCache
164170
include Sqlserver::Errors
165171

166172
ADAPTER_NAME = 'SQLServer'.freeze
@@ -265,6 +271,11 @@ def reset!
265271
remove_database_connections_and_rollback { }
266272
end
267273

274+
def clear_cache!
275+
# This requires db admin perms and I'm not even sure it is a good idea.
276+
# raw_connection_do "DBCC FREEPROCCACHE WITH NO_INFOMSGS" rescue nil
277+
end
278+
268279
# === Abstract Adapter (Misc Support) =========================== #
269280

270281
def pk_and_sequence_for(table_name)
@@ -331,7 +342,7 @@ def native_binary_database_type
331342
end
332343

333344
def cs_equality_operator
334-
@@cs_equality_operator || 'COLLATE Latin1_General_CS_AS_WS ='
345+
@@cs_equality_operator || 'COLLATE Latin1_General_CS_AS_WS'
335346
end
336347

337348

0 commit comments

Comments
 (0)