Skip to content

Commit 84f24e3

Browse files
committed
Adding sqlserver columns cache support with hooks into .reset_column_information. Also tests for simple helpers now named #unqualify_table_name and #unqualify_db_name.
1 parent ac617bd commit 84f24e3

File tree

2 files changed

+100
-49
lines changed

2 files changed

+100
-49
lines changed

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 65 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ def self.sqlserver_connection(config) #:nodoc:
2727
ConnectionAdapters::SQLServerAdapter.new(conn, logger, [driver_url, username, password])
2828
end
2929

30+
class << self
31+
32+
def reset_column_information_with_sqlserver_columns_cache_support
33+
connection.instance_variable_set :@sqlserver_columns_cache, {}
34+
reset_column_information_without_sqlserver_columns_cache_support
35+
end
36+
alias_method_chain :reset_column_information, :sqlserver_columns_cache_support
37+
38+
end
39+
3040
private
3141

3242
# Add basic support for SQL server locking hints
@@ -265,6 +275,7 @@ class SQLServerAdapter < AbstractAdapter
265275
def initialize(connection, logger, connection_options=nil)
266276
super(connection, logger)
267277
@connection_options = connection_options
278+
@sqlserver_columns_cache = {}
268279
unless SUPPORTED_VERSIONS.include?(database_year)
269280
raise NotImplementedError, "Currently, only #{SUPPORTED_VERSIONS.to_sentence} are supported."
270281
end
@@ -573,20 +584,25 @@ def table_exists?(table_name)
573584

574585
def columns(table_name, name = nil)
575586
return [] if table_name.blank?
576-
table_names = table_name.to_s.split('.')
577-
table_name = table_names[-1]
578-
table_name = table_name.gsub(/[\[\]]/, '')
579-
db_name = "#{table_names[0]}." if table_names.length==3
580-
column_definitions(table_name,db_name).collect do |ci|
587+
cache_key = unqualify_table_name(table_name)
588+
@sqlserver_columns_cache[cache_key] ||= column_definitions(table_name).collect do |ci|
581589
sqlserver_options = ci.except(:name,:default_value,:type,:null)
582590
SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
583591
end
584592
end
585593

586-
594+
def create_table(table_name, options = {})
595+
super
596+
remove_sqlserver_columns_cache_for(table_name)
597+
end
587598

588-
def rename_table(name, new_name)
589-
execute "EXEC sp_rename '#{name}', '#{new_name}'"
599+
def drop_table(table_name, options = {})
600+
super
601+
remove_sqlserver_columns_cache_for(table_name)
602+
end
603+
604+
def rename_table(table_name, new_name)
605+
execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
590606
end
591607

592608
def add_column(table_name, column_name, type, options = {})
@@ -595,26 +611,27 @@ def add_column(table_name, column_name, type, options = {})
595611
# TODO: Add support to mimic date columns, using constraints to mark them as such in the database
596612
# add_column_sql << " CONSTRAINT ck__#{table_name}__#{column_name}__date_only CHECK ( CONVERT(CHAR(12), #{quote_column_name(column_name)}, 14)='00:00:00:000' )" if type == :date
597613
execute(add_column_sql)
614+
remove_sqlserver_columns_cache_for(table_name)
598615
end
599616

600617
def remove_column(table_name, column_name)
601618
remove_check_constraints(table_name, column_name)
602619
remove_default_constraint(table_name, column_name)
603620
remove_indexes(table_name, column_name)
604621
execute "ALTER TABLE [#{table_name}] DROP COLUMN #{quote_column_name(column_name)}"
622+
remove_sqlserver_columns_cache_for(table_name)
605623
end
606624

607-
def change_column(table_name, column_name, type, options = {}) #:nodoc:
625+
def change_column(table_name, column_name, type, options = {})
608626
sql = "ALTER TABLE #{table_name} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
609627
sql << " NOT NULL" if options[:null] == false
610628
sql_commands = [sql]
611629
if options_include_default?(options)
612630
remove_default_constraint(table_name, column_name)
613631
sql_commands << "ALTER TABLE #{table_name} ADD CONSTRAINT DF_#{table_name}_#{column_name} DEFAULT #{quote(options[:default], options[:column])} FOR #{quote_column_name(column_name)}"
614632
end
615-
sql_commands.each {|c|
616-
execute(c)
617-
}
633+
sql_commands.each { |c| execute(c) }
634+
remove_sqlserver_columns_cache_for(table_name)
618635
end
619636

620637
def change_column_default(table_name, column_name, default)
@@ -852,17 +869,39 @@ def query_requires_identity_insert?(sql)
852869
end
853870

854871
def identity_column(table_name)
855-
@table_columns ||= {}
856-
@table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
857-
@table_columns[table_name].each do |col|
858-
return col.name if col.is_identity?
859-
end
860-
return nil
872+
idcol = columns(table_name).detect(&:is_identity?)
873+
idcol ? idcol.name : nil
861874
end
862875

863876
# SQL UTILITY METHODS ======================================#
864877

865-
def column_definitions(table_name, db_name = nil)
878+
def unqualify_table_name(table_name)
879+
table_name.to_s.split('.').last.gsub(/[\[\]]/,'')
880+
end
881+
882+
def unqualify_db_name(table_name)
883+
table_names = table_name.to_s.split('.')
884+
table_names.length == 3 ? table_names.first.gsub(/[\[\]]/,'') : nil
885+
end
886+
887+
def get_table_name(sql)
888+
if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
889+
$1 || $2
890+
elsif sql =~ /from\s+([^\(\s]+)\s*/i
891+
$1
892+
else
893+
nil
894+
end
895+
end
896+
897+
def remove_sqlserver_columns_cache_for(table_name)
898+
cache_key = unqualify_table_name(table_name)
899+
@sqlserver_columns_cache[cache_key] = nil
900+
end
901+
902+
def column_definitions(table_name)
903+
db_name = unqualify_db_name(table_name)
904+
table_name = unqualify_table_name(table_name)
866905
# COL_LENGTH returns values that do not reflect how much data can be stored in certain data types.
867906
# COL_LENGTH returns -1 for varchar(max), nvarchar(max), and varbinary(max)
868907
# COL_LENGTH returns 16 for ntext, text, image types
@@ -909,19 +948,8 @@ def column_definitions(table_name, db_name = nil)
909948
end
910949
end
911950

912-
def get_table_name(sql)
913-
if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
914-
$1 || $2
915-
elsif sql =~ /from\s+([^\(\s]+)\s*/i
916-
$1
917-
else
918-
nil
919-
end
920-
end
921-
922951

923952

924-
925953

926954
def change_order_direction(order)
927955
order.split(",").collect {|fragment|
@@ -933,37 +961,25 @@ def change_order_direction(order)
933961
}.join(",")
934962
end
935963

936-
def get_special_columns(table_name)
937-
special = []
938-
@table_columns ||= {}
939-
@table_columns[table_name] ||= columns(table_name)
940-
@table_columns[table_name].each do |col|
941-
special << col.name if col.is_special?
942-
end
943-
special
964+
def special_columns(table_name)
965+
columns(table_name).select(&:is_special?).map(&:name)
944966
end
945967

946968
def repair_special_columns(sql)
947-
special_cols = get_special_columns(get_table_name(sql))
969+
special_cols = special_columns(get_table_name(sql))
948970
for col in special_cols.to_a
949971
sql.gsub!(/((\.|\s|\()\[?#{col.to_s}\]?)\s?=\s?/, '\1 LIKE ')
950972
sql.gsub!(/ORDER BY #{col.to_s}/i, '')
951973
end
952974
sql
953975
end
954976

955-
def get_utf8_columns(table_name)
956-
utf8 = []
957-
@table_columns ||= {}
958-
@table_columns[table_name] ||= columns(table_name)
959-
@table_columns[table_name].each do |col|
960-
utf8 << col.name if col.is_utf8?
961-
end
962-
utf8
977+
def utf8_columns(table_name)
978+
columns(table_name).select(&:is_utf8?).map(&:name)
963979
end
964-
980+
965981
def set_utf8_values!(sql)
966-
utf8_cols = get_utf8_columns(get_table_name(sql))
982+
utf8_cols = utf8_columns(get_table_name(sql))
967983
if sql =~ /^\s*UPDATE/i
968984
utf8_cols.each do |col|
969985
sql.gsub!("[#{col.to_s}] = '", "[#{col.to_s}] = N'")

test/cases/adapter_test_sqlserver.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,41 @@ def setup
5252

5353
end
5454

55+
context 'for #unqualify_table_name and #unqualify_db_name' do
56+
57+
setup do
58+
@expected_table_name = 'baz'
59+
@expected_db_name = 'foo'
60+
@first_second_table_names = ['[baz]','baz','[bar].[baz]','bar.baz']
61+
@third_table_names = ['[foo].[bar].[baz]','foo.bar.baz']
62+
@qualifed_table_names = @first_second_table_names + @third_table_names
63+
end
64+
65+
should 'return clean table_name from #unqualify_table_name' do
66+
@qualifed_table_names.each do |qtn|
67+
assert_equal @expected_table_name,
68+
@connection.send(:unqualify_table_name,qtn),
69+
"This qualifed_table_name #{qtn} did not unqualify correctly."
70+
end
71+
end
72+
73+
should 'return nil from #unqualify_db_name when table_name is less than 2 qualified' do
74+
@first_second_table_names.each do |qtn|
75+
assert_equal nil, @connection.send(:unqualify_db_name,qtn),
76+
"This qualifed_table_name #{qtn} did not return nil."
77+
end
78+
end
79+
80+
should 'return clean db_name from #unqualify_db_name when table is thrid level qualified' do
81+
@third_table_names.each do |qtn|
82+
assert_equal @expected_db_name,
83+
@connection.send(:unqualify_db_name,qtn),
84+
"This qualifed_table_name #{qtn} did not unqualify the db_name correctly."
85+
end
86+
end
87+
88+
end
89+
5590
end
5691

5792
context 'For identity inserts' do

0 commit comments

Comments
 (0)