Skip to content

Commit aa53583

Browse files
committed
More tests passing. Standardize SchemaCache in/out policy.
1 parent f8c7fe5 commit aa53583

30 files changed

+296
-399
lines changed

lib/active_record/connection_adapters/sqlserver/schema_cache.rb

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ module ConnectionAdapters
33
module SQLServer
44
class SchemaCache < ActiveRecord::ConnectionAdapters::SchemaCache
55

6-
attr_reader :view_information
7-
86
def initialize(conn)
97
super
108
@views = {}
@@ -14,28 +12,32 @@ def initialize(conn)
1412
# Superclass Overrides
1513

1614
def primary_keys(table_name)
17-
super(table_name_key(table_name))
15+
name = key(table_name)
16+
@primary_keys[name] ||= table_exists?(table_name) ? connection.primary_key(table_name) : nil
1817
end
1918

2019
def table_exists?(table_name)
21-
name = table_name_key(table_name)
22-
super(name) || view_exists?(name)
23-
end
24-
25-
def add(table_name)
26-
super(table_name_key(table_name))
20+
name = key(table_name)
21+
prepare_tables_and_views
22+
return @tables[name] if @tables.key? name
23+
table_exists = @tables[name] = connection.table_exists?(table_name)
24+
table_exists || view_exists?(table_name)
2725
end
2826

2927
def tables(name)
30-
super(table_name_key(name))
28+
super(key(name))
3129
end
3230

3331
def columns(table_name)
34-
super(table_name_key(table_name))
32+
name = key(table_name)
33+
@columns[name] ||= connection.columns(table_name)
3534
end
3635

3736
def columns_hash(table_name)
38-
super(table_name_key(table_name))
37+
name = key(table_name)
38+
@columns_hash[name] ||= Hash[columns(table_name).map { |col|
39+
[col.name, col]
40+
}]
3941
end
4042

4143
def clear!
@@ -49,10 +51,13 @@ def size
4951
end
5052

5153
def clear_table_cache!(table_name)
52-
table_name = table_name_key(table_name)
53-
super(table_name)
54-
@views.delete table_name
55-
@view_information.delete table_name
54+
name = key(table_name)
55+
@columns.delete name
56+
@columns_hash.delete name
57+
@primary_keys.delete name
58+
@tables.delete name
59+
@views.delete name
60+
@view_information.delete name
5661
end
5762

5863
def marshal_dump
@@ -66,32 +71,41 @@ def marshal_load(array)
6671

6772
# SQL Server Specific
6873

69-
def view_names
70-
@views.select{ |k,v| v }.keys
71-
end
72-
7374
def view_exists?(table_name)
74-
name = table_name_key(table_name)
75-
prepare_views if @views.empty?
75+
name = key(table_name)
76+
prepare_tables_and_views
7677
return @views[name] if @views.key? name
77-
@views[name] = connection.views.include?(name)
78+
@views[name] = connection.views.include?(table_name)
7879
end
7980

8081
def view_information(table_name)
81-
name = table_name_key(table_name)
82+
name = key(table_name)
8283
return @view_information[name] if @view_information.key? name
8384
@view_information[name] = connection.send(:view_information, table_name)
8485
end
8586

8687

8788
private
8889

89-
def table_name_key(table_name)
90-
SQLServer::Utils.extract_identifiers(table_name).quoted
90+
def identifier(table_name)
91+
SQLServer::Utils.extract_identifiers(table_name)
92+
end
93+
94+
def key(table_name)
95+
identifier(table_name).quoted
96+
end
97+
98+
def prepare_tables_and_views
99+
prepare_views if @views.empty?
100+
prepare_tables if @tables.empty?
101+
end
102+
103+
def prepare_tables
104+
connection.tables.each { |table| @tables[key(table)] = true }
91105
end
92106

93107
def prepare_views
94-
connection.views.each { |view| @views[view] = true }
108+
connection.views.each { |view| @views[key(view)] = true }
95109
end
96110

97111
end

lib/active_record/connection_adapters/sqlserver/schema_statements.rb

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,9 @@ def initialize_native_database_types
187187
end
188188

189189
def column_definitions(table_name)
190-
db_name = SQLServer::Utils.extract_identifiers(table_name).database
191-
db_name_with_period = "#{db_name}." if db_name
192-
table_schema = SQLServer::Utils.extract_identifiers(table_name).schema
193-
table_name = SQLServer::Utils.extract_identifiers(table_name).object
190+
identifier = SQLServer::Utils.extract_identifiers(table_name)
191+
database = "#{identifier.database_quoted}." if identifier.database_quoted
192+
table_name = identifier.quoted
194193
sql = %{
195194
SELECT DISTINCT
196195
#{lowercase_schema_reflection_sql('columns.TABLE_NAME')} AS table_name,
@@ -203,7 +202,7 @@ def column_definitions(table_name)
203202
columns.ordinal_position,
204203
CASE
205204
WHEN columns.DATA_TYPE IN ('nchar','nvarchar') THEN columns.CHARACTER_MAXIMUM_LENGTH
206-
ELSE COL_LENGTH('#{db_name_with_period}'+columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
205+
ELSE COL_LENGTH('#{database}'+columns.TABLE_SCHEMA+'.'+columns.TABLE_NAME, columns.COLUMN_NAME)
207206
END AS [length],
208207
CASE
209208
WHEN columns.IS_NULLABLE = 'YES' THEN 1
@@ -214,32 +213,32 @@ def column_definitions(table_name)
214213
ELSE NULL
215214
END AS [is_primary],
216215
c.is_identity AS [is_identity]
217-
FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS columns
218-
LEFT OUTER JOIN #{db_name_with_period}INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
216+
FROM #{database}INFORMATION_SCHEMA.COLUMNS columns
217+
LEFT OUTER JOIN #{database}INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
219218
ON TC.TABLE_NAME = columns.TABLE_NAME
220219
AND TC.CONSTRAINT_TYPE = N'PRIMARY KEY'
221-
LEFT OUTER JOIN #{db_name_with_period}INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
220+
LEFT OUTER JOIN #{database}INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS KCU
222221
ON KCU.COLUMN_NAME = columns.COLUMN_NAME
223222
AND KCU.CONSTRAINT_NAME = TC.CONSTRAINT_NAME
224223
AND KCU.CONSTRAINT_CATALOG = TC.CONSTRAINT_CATALOG
225224
AND KCU.CONSTRAINT_SCHEMA = TC.CONSTRAINT_SCHEMA
226-
INNER JOIN #{db_name}.sys.schemas AS s
225+
INNER JOIN #{identifier.database_quoted}.sys.schemas AS s
227226
ON s.name = columns.TABLE_SCHEMA
228227
AND s.schema_id = s.schema_id
229-
INNER JOIN #{db_name}.sys.objects AS o
228+
INNER JOIN #{identifier.database_quoted}.sys.objects AS o
230229
ON s.schema_id = o.schema_id
231230
AND o.is_ms_shipped = 0
232231
AND o.type IN ('U', 'V')
233232
AND o.name = columns.TABLE_NAME
234-
INNER JOIN #{db_name}.sys.columns AS c
233+
INNER JOIN #{identifier.database_quoted}.sys.columns AS c
235234
ON o.object_id = c.object_id
236235
AND c.name = columns.COLUMN_NAME
237236
WHERE columns.TABLE_NAME = @0
238-
AND columns.TABLE_SCHEMA = #{table_schema.blank? ? 'schema_name()' : '@1'}
237+
AND columns.TABLE_SCHEMA = #{identifier.schema.blank? ? 'schema_name()' : '@1'}
239238
ORDER BY columns.ordinal_position
240239
}.gsub(/[ \t\r\n]+/, ' ')
241-
binds = [['table_name', table_name]]
242-
binds << ['table_schema', table_schema] unless table_schema.blank?
240+
binds = [['table_name', identifier.object]]
241+
binds << ['table_schema', identifier.schema] unless identifier.schema.blank?
243242
results = do_exec_query(sql, 'SCHEMA', binds)
244243
results.map do |ci|
245244
ci = ci.symbolize_keys
@@ -257,10 +256,10 @@ def column_definitions(table_name)
257256
else
258257
ci[:type]
259258
end
260-
if ci[:default_value].nil? && schema_cache.view_names.include?(table_name)
259+
if ci[:default_value].nil? && schema_cache.view_exists?(table_name)
261260
real_table_name = table_name_or_views_table_name(table_name)
262261
real_column_name = views_real_column_name(table_name, ci[:name])
263-
col_default_sql = "SELECT c.COLUMN_DEFAULT FROM #{db_name_with_period}INFORMATION_SCHEMA.COLUMNS c WHERE c.TABLE_NAME = '#{real_table_name}' AND c.COLUMN_NAME = '#{real_column_name}'"
262+
col_default_sql = "SELECT c.COLUMN_DEFAULT FROM #{database}INFORMATION_SCHEMA.COLUMNS c WHERE c.TABLE_NAME = '#{real_table_name}' AND c.COLUMN_NAME = '#{real_column_name}'"
264263
ci[:default_value] = select_value col_default_sql, 'SCHEMA'
265264
end
266265
ci[:default_value] = case ci[:default_value]
@@ -306,13 +305,14 @@ def remove_indexes(table_name, column_name)
306305
# === SQLServer Specific (Misc Helpers) ========================= #
307306

308307
def get_table_name(sql)
309-
if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
308+
tn = if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)(\s+INTO)?\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
310309
Regexp.last_match[3] || Regexp.last_match[4]
311310
elsif sql =~ /FROM\s+([^\(\s]+)\s*/i
312311
Regexp.last_match[1]
313312
else
314313
nil
315314
end
315+
SQLServer::Utils.extract_identifiers(tn).object
316316
end
317317

318318
def default_constraint_name(table_name, column_name)
@@ -344,19 +344,18 @@ def view_information(table_name)
344344
view_info = view_info.with_indifferent_access
345345
if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
346346
view_info[:VIEW_DEFINITION] = begin
347-
select_values("EXEC sp_helptext #{quote_table_name(table_name)}", 'SCHEMA').join
348-
rescue
349-
warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
350-
nil
351-
end
347+
select_values("EXEC sp_helptext #{quote_table_name(table_name)}", 'SCHEMA').join
348+
rescue
349+
warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
350+
nil
351+
end
352352
end
353353
end
354354
view_info
355355
end
356356

357357
def table_name_or_views_table_name(table_name)
358-
unquoted_table_name = SQLServer::Utils.extract_identifiers(table_name).object
359-
schema_cache.view_names.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
358+
schema_cache.view_exists?(table_name) ? view_table_name(table_name) : table_name
360359
end
361360

362361
def views_real_column_name(table_name, column_name)

lib/active_record/connection_adapters/sqlserver_column.rb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,6 @@ def sql_type_for_statement
1616
end
1717
end
1818

19-
def primary?
20-
is_identity? || is_primary?
21-
end
22-
2319
def is_identity?
2420
@sqlserver_options[:is_identity]
2521
end

test/cases/adapter_test_sqlserver.rb

Lines changed: 21 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'cases/helper_sqlserver'
22
require 'models/topic'
33
require 'models/task'
4+
require 'models/post'
45
require 'models/subscriber'
56
require 'models/minimalistic'
67

@@ -65,10 +66,10 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
6566
assert !connection.send(:insert_sql?,'SELECT...')
6667
end
6768

68-
it 'return quoted table name from basic INSERT UPDATE and SELECT statements' do
69-
assert_equal '[funny_jokes]', connection.send(:get_table_name, basic_insert_sql)
70-
assert_equal '[customers]', connection.send(:get_table_name, basic_update_sql)
71-
assert_equal '[customers]', connection.send(:get_table_name, basic_select_sql)
69+
it 'return unquoted table name object from basic INSERT UPDATE and SELECT statements' do
70+
assert_equal 'funny_jokes', connection.send(:get_table_name, basic_insert_sql)
71+
assert_equal 'customers', connection.send(:get_table_name, basic_update_sql)
72+
assert_equal 'customers', connection.send(:get_table_name, basic_select_sql)
7273
end
7374

7475
describe 'with different language' do
@@ -413,10 +414,9 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
413414
end
414415

415416
it 'find identity column' do
416-
pk_name = connection.primary_key(SSTestCustomersView.table_name)
417-
pk_name.must_equal 'id'
418-
pk_column = SSTestCustomersView.columns_hash[pk_name]
419-
pk_column.must_be :primary?
417+
SSTestCustomersView.primary_key.must_equal 'id'
418+
connection.primary_key(SSTestCustomersView.table_name).must_equal 'id'
419+
SSTestCustomersView.columns_hash['id'].must_be :is_identity?
420420
end
421421

422422
it 'find default values' do
@@ -427,40 +427,32 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
427427
assert SSTestCustomersView.table_exists?
428428
end
429429

430-
it 'have correct table name for all column objects' do
431-
assert SSTestCustomersView.columns.all?{ |c| c.table_name == 'sst_customers_view' },
432-
SSTestCustomersView.columns.map(&:table_name).inspect
433-
end
434-
435430
# With aliased column names
436431

437432
it 'have matching column objects' do
438433
columns = ['id','pretend_null']
439-
assert !StringDefaultsView.columns.blank?
440-
assert_equal columns.size, StringDefaultsView.columns.size
434+
assert !SSTestStringDefaultsView.columns.blank?
435+
assert_equal columns.size, SSTestStringDefaultsView.columns.size
441436
columns.each do |colname|
442437
assert_instance_of ActiveRecord::ConnectionAdapters::SQLServerColumn,
443-
StringDefaultsView.columns_hash[colname],
444-
"Column name #{colname.inspect} was not found in these columns #{StringDefaultsView.columns.map(&:name).inspect}"
438+
SSTestStringDefaultsView.columns_hash[colname],
439+
"Column name #{colname.inspect} was not found in these columns #{SSTestStringDefaultsView.columns.map(&:name).inspect}"
445440
end
446441
end
447442

448443
it 'find identity column' do
449-
assert StringDefaultsView.columns_hash['id'].primary
444+
SSTestStringDefaultsView.primary_key.must_equal 'id'
445+
connection.primary_key(SSTestStringDefaultsView.table_name).must_equal 'id'
446+
SSTestStringDefaultsView.columns_hash['id'].must_be :is_identity?
450447
end
451448

452449
it 'find default values' do
453-
assert_equal 'null', StringDefaultsView.new.pretend_null,
454-
StringDefaultsView.columns_hash['pretend_null'].inspect
450+
assert_equal 'null', SSTestStringDefaultsView.new.pretend_null,
451+
SSTestStringDefaultsView.columns_hash['pretend_null'].inspect
455452
end
456453

457454
it 'respond true to table_exists?' do
458-
assert StringDefaultsView.table_exists?
459-
end
460-
461-
it 'have correct table name for all column objects' do
462-
assert StringDefaultsView.columns.all?{ |c| c.table_name == 'string_defaults_view' },
463-
StringDefaultsView.columns.map(&:table_name).inspect
455+
assert SSTestStringDefaultsView.table_exists?
464456
end
465457

466458
# Doing identity inserts
@@ -479,31 +471,15 @@ class AdapterTestSQLServer < ActiveRecord::TestCase
479471
# That have more than 4000 chars for their defintion
480472

481473
it 'cope with null returned for the defintion' do
482-
assert_nothing_raised() { StringDefaultsBigView.columns }
474+
assert_nothing_raised() { SSTestStringDefaultsBigView.columns }
483475
end
484476

485477
it 'using alternate view defintion still be able to find real default' do
486-
assert_equal 'null', StringDefaultsBigView.new.pretend_null,
487-
StringDefaultsBigView.columns_hash['pretend_null'].inspect
478+
assert_equal 'null', SSTestStringDefaultsBigView.new.pretend_null,
479+
SSTestStringDefaultsBigView.columns_hash['pretend_null'].inspect
488480
end
489481

490482
end
491483

492484
end
493485

494-
495-
module ActiveRecord
496-
class AdapterTest < ActiveRecord::TestCase
497-
498-
COERCED_TESTS = [:test_update_prepared_statement]
499-
# Like PostgreSQL, SQL Server does not support null bytes in strings.
500-
# DECLARE @mybin1 binary(5), @mybin2 binary(5)
501-
# SET @mybin1 = 0x00
502-
# SELECT 'a'+CONVERT(varchar(5), @mybin1) + 'aaaaa'
503-
# This is not run for PostgreSQL at the rails level and the same should happen for SQL Server
504-
# Until that patch is made to rails we are preventing this test from running in this gem.
505-
include ARTest::SQLServer::CoercedTest
506-
507-
fixtures :authors
508-
end
509-
end

test/cases/named_scoping_test_sqlserver.rb

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

test/cases/schema_test_sqlserver.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class SchemaTestSQLServer < ActiveRecord::TestCase
1212

1313
should 'find primary key for tables with odd schema' do
1414
assert_equal 'legacy_id', @connection.primary_key('natural_pk_data')
15-
assert SqlServerNaturalPkData.columns_hash['legacy_id'].primary
15+
assert SSTestNaturalPkData.columns_hash['legacy_id'].primary
1616
end
1717

1818
end

0 commit comments

Comments
 (0)