Skip to content

Commit

Permalink
Initial 3.2 compatability. All tests green.
Browse files Browse the repository at this point in the history
  * Make use of the new ConnectionAdapters::SchemaCache for our needs.
  * New Sqlserver::Utils class for out helpers. Moved table name unquotes there.
  • Loading branch information
metaskills committed Dec 29, 2011
1 parent 55aa57f commit 7a18ff1
Show file tree
Hide file tree
Showing 13 changed files with 227 additions and 107 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG
@@ -1,4 +1,9 @@

* 3.2.0 *

*


* 3.1.5 *

* Better support for orders with an expression. Fixes #155. [Jason Frey, Joe Rafaniello]
Expand Down
3 changes: 1 addition & 2 deletions lib/active_record/connection_adapters/sqlserver/quoting.rb
Expand Up @@ -42,8 +42,7 @@ def quote_string(string)
end

def quote_column_name(name)
@sqlserver_quoted_column_and_table_names[name] ||=
name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n.to_s.gsub(']', ']]')}]" }.join('.')
schema_cache.quote_name(name)
end

def quote_table_name(name)
Expand Down
85 changes: 85 additions & 0 deletions lib/active_record/connection_adapters/sqlserver/schema_cache.rb
@@ -0,0 +1,85 @@
module ActiveRecord
module ConnectionAdapters
module Sqlserver
class SchemaCache < ActiveRecord::ConnectionAdapters::SchemaCache

attr_reader :view_information

def initialize(conn)
super
@table_names = nil
@view_names = nil
@view_information = {}
@quoted_names = {}
end

# Superclass Overrides

def table_exists?(table_name)
return false if table_name.blank?
key = table_name_key(table_name)
return @tables[key] if @tables.key? key
@tables[key] = connection.table_exists?(table_name)
end

def clear!
super
@table_names = nil
@view_names = nil
@view_information.clear
@quoted_names.clear
end

def clear_table_cache!(table_name)
key = table_name_key(table_name)
super(key)
super(table_name)
# SQL Server Specific
if @table_names
@table_names.delete key
@table_names.delete table_name
end
if @view_names
@view_names.delete key
@view_names.delete table_name
end
@view_information.delete key
end

# SQL Server Specific

def table_names
@table_names ||= connection.tables
end

def view_names
@view_names ||= connection.views
end

def view_exists?(table_name)
table_exists?(table_name)
end

def view_information(table_name)
key = table_name_key(table_name)
return @view_information[key] if @view_information.key? key
@view_information[key] = connection.send(:view_information, table_name)
end

def quote_name(name)
return @quoted_names[name] if @quoted_names.key? name
@quoted_names[name] = name.to_s.split('.').map{ |n| n =~ /^\[.*\]$/ ? n : "[#{n.to_s.gsub(']', ']]')}]" }.join('.')
end


private

def table_name_key(table_name)
Utils.unqualify_table_name(table_name)
end

end
end
end
end

104 changes: 31 additions & 73 deletions lib/active_record/connection_adapters/sqlserver/schema_statements.rb
Expand Up @@ -7,15 +7,15 @@ def native_database_types
@native_database_types ||= initialize_native_database_types.freeze
end

def tables(name = nil, table_type = 'BASE TABLE')
def tables(table_type = 'BASE TABLE')
info_schema_query do
select_values "SELECT #{lowercase_schema_reflection_sql('TABLE_NAME')} FROM INFORMATION_SCHEMA.TABLES #{"WHERE TABLE_TYPE = '#{table_type}'" if table_type} ORDER BY TABLE_NAME"
end
end

def table_exists?(table_name)
return false if table_name.blank?
unquoted_table_name = unqualify_table_name(table_name)
unquoted_table_name = Utils.unqualify_table_name(table_name)
super || tables.include?(unquoted_table_name) || views.include?(unquoted_table_name)
end

Expand All @@ -40,31 +40,16 @@ def indexes(table_name, name = nil)

def columns(table_name, name = nil)
return [] if table_name.blank?
@sqlserver_columns_cache[table_name] ||= column_definitions(table_name).collect do |ci|
column_definitions(table_name).collect do |ci|
sqlserver_options = ci.except(:name,:default_value,:type,:null).merge(:database_year=>database_year)
SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
end
end

def create_table(table_name, options = {})
super
clear_cache!
end

def rename_table(table_name, new_name)
do_execute "EXEC sp_rename '#{table_name}', '#{new_name}'"
end

def drop_table(table_name, options = {})
super
clear_cache!
end

def add_column(table_name, column_name, type, options = {})
super
clear_cache!
end

def remove_column(table_name, *column_names)
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
column_names.flatten.each do |column_name|
Expand All @@ -73,12 +58,11 @@ def remove_column(table_name, *column_names)
remove_indexes(table_name, column_name)
do_execute "ALTER TABLE #{quote_table_name(table_name)} DROP COLUMN #{quote_column_name(column_name)}"
end
clear_cache!
end

def change_column(table_name, column_name, type, options = {})
sql_commands = []
column_object = columns(table_name).detect { |c| c.name.to_s == column_name.to_s }
column_object = schema_cache.columns[table_name].detect { |c| c.name.to_s == column_name.to_s }
change_column_sql = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} #{type_to_sql(type, options[:limit], options[:precision], options[:scale])}"
change_column_sql << " NOT NULL" if options[:null] == false
sql_commands << change_column_sql
Expand All @@ -89,19 +73,16 @@ def change_column(table_name, column_name, type, options = {})
sql_commands << "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name,column_name)} DEFAULT #{quote(options[:default])} FOR #{quote_column_name(column_name)}"
end
sql_commands.each { |c| do_execute(c) }
clear_cache!
end

def change_column_default(table_name, column_name, default)
remove_default_constraint(table_name, column_name)
do_execute "ALTER TABLE #{quote_table_name(table_name)} ADD CONSTRAINT #{default_constraint_name(table_name, column_name)} DEFAULT #{quote(default)} FOR #{quote_column_name(column_name)}"
clear_cache!
end

def rename_column(table_name, column_name, new_column_name)
detect_column_for!(table_name,column_name)
detect_column_for! table_name, column_name
do_execute "EXEC sp_rename '#{table_name}.#{column_name}', '#{new_column_name}', 'COLUMN'"
clear_cache!
end

def remove_index!(table_name, index_name)
Expand All @@ -125,7 +106,7 @@ def type_to_sql(type, limit = nil, precision = nil, scale = nil)
end

def change_column_null(table_name, column_name, null, default = nil)
column = detect_column_for!(table_name,column_name)
column = detect_column_for! table_name, column_name
unless null || default.nil?
do_execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
end
Expand All @@ -136,8 +117,8 @@ def change_column_null(table_name, column_name, null, default = nil)

# === SQLServer Specific ======================================== #

def views(name = nil)
@sqlserver_views_cache ||= tables(name,'VIEW')
def views
tables('VIEW')
end


Expand Down Expand Up @@ -171,10 +152,10 @@ def initialize_native_database_types
end

def column_definitions(table_name)
db_name = unqualify_db_name(table_name)
db_name = Utils.unqualify_db_name(table_name)
db_name_with_period = "#{db_name}." if db_name
table_schema = unqualify_table_schema(table_name)
table_name = unqualify_table_name(table_name)
table_schema = Utils.unqualify_table_schema(table_name)
table_name = Utils.unqualify_table_name(table_name)
sql = %{
SELECT DISTINCT
#{lowercase_schema_reflection_sql('columns.TABLE_NAME')} AS table_name,
Expand Down Expand Up @@ -230,7 +211,7 @@ def column_definitions(table_name)
else
ci[:type]
end
if ci[:default_value].nil? && views.include?(table_name)
if ci[:default_value].nil? && schema_cache.view_names.include?(table_name)
real_table_name = table_name_or_views_table_name(table_name)
real_column_name = views_real_column_name(table_name,ci[:name])
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}'"
Expand Down Expand Up @@ -281,19 +262,6 @@ def info_schema_query
log_info_schema_queries ? yield : ActiveRecord::Base.silence{ yield }
end

def unqualify_table_name(table_name)
table_name.to_s.split('.').last.tr('[]','')
end

def unqualify_table_schema(table_name)
table_name.to_s.split('.')[-2].gsub(/[\[\]]/,'') rescue nil
end

def unqualify_db_name(table_name)
table_names = table_name.to_s.split('.')
table_names.length == 3 ? table_names.first.tr('[]','') : nil
end

def get_table_name(sql)
if sql =~ /^\s*(INSERT|EXEC sp_executesql N'INSERT)\s+INTO\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
$2 || $3
Expand All @@ -309,7 +277,7 @@ def default_constraint_name(table_name, column_name)
end

def detect_column_for!(table_name, column_name)
unless column = columns(table_name).detect { |c| c.name == column_name.to_s }
unless column = schema_cache.columns[table_name].detect { |c| c.name == column_name.to_s }
raise ActiveRecordError, "No such column: #{table_name}.#{column_name}"
end
column
Expand All @@ -322,50 +290,40 @@ def lowercase_schema_reflection_sql(node)
# === SQLServer Specific (View Reflection) ====================== #

def view_table_name(table_name)
view_info = view_information(table_name)
view_info = schema_cache.view_information(table_name)
view_info ? get_table_name(view_info['VIEW_DEFINITION']) : table_name
end

def view_information(table_name)
table_name = unqualify_table_name(table_name)
@sqlserver_view_information_cache[table_name] ||= begin
view_info = info_schema_query { select_one("SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'") }
if view_info
view_info = view_info.with_indifferent_access
if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
view_info[:VIEW_DEFINITION] = info_schema_query do
begin
select_values("EXEC sp_helptext #{quote_table_name(table_name)}").join
rescue
warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
end
table_name = Utils.unqualify_table_name(table_name)
view_info = info_schema_query { select_one("SELECT * FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_NAME = '#{table_name}'") }
if view_info
view_info = view_info.with_indifferent_access
if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
view_info[:VIEW_DEFINITION] = info_schema_query do
begin
select_values("EXEC sp_helptext #{quote_table_name(table_name)}").join
rescue
warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
nil
end
end
end
end
view_info
end
view_info
end

def table_name_or_views_table_name(table_name)
unquoted_table_name = unqualify_table_name(table_name)
views.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
unquoted_table_name = Utils.unqualify_table_name(table_name)
schema_cache.view_names.include?(unquoted_table_name) ? view_table_name(unquoted_table_name) : unquoted_table_name
end

def views_real_column_name(table_name,column_name)
view_definition = view_information(table_name)[:VIEW_DEFINITION]
view_definition = schema_cache.view_information(table_name)[:VIEW_DEFINITION]
match_data = view_definition.match(/([\w-]*)\s+as\s+#{column_name}/im)
match_data ? match_data[1] : column_name
end

# === SQLServer Specific (Column/View Caches) =================== #

def initialize_sqlserver_caches
@sqlserver_columns_cache = {}
@sqlserver_views_cache = nil
@sqlserver_view_information_cache = {}
@sqlserver_quoted_column_and_table_names = {}
end

# === SQLServer Specific (Identity Inserts) ===================== #

def query_requires_identity_insert?(sql)
Expand Down Expand Up @@ -398,7 +356,7 @@ def set_identity_insert(table_name, enable = true)
end

def identity_column(table_name)
columns(table_name).detect(&:is_identity?)
schema_cache.columns[table_name].detect(&:is_identity?)
end

end
Expand Down
28 changes: 28 additions & 0 deletions lib/active_record/connection_adapters/sqlserver/utils.rb
@@ -0,0 +1,28 @@
module ActiveRecord
module ConnectionAdapters
module Sqlserver
class Utils

class << self

def unqualify_table_name(table_name)
table_name.to_s.split('.').last.tr('[]','')
end

def unqualify_table_schema(table_name)
table_name.to_s.split('.')[-2].gsub(/[\[\]]/,'') rescue nil
end

def unqualify_db_name(table_name)
table_names = table_name.to_s.split('.')
table_names.length == 3 ? table_names.first.tr('[]','') : nil
end

end

end
end
end
end


2 changes: 1 addition & 1 deletion lib/active_record/connection_adapters/sqlserver/version.rb
Expand Up @@ -3,7 +3,7 @@ module ConnectionAdapters
module Sqlserver
module Version

VERSION = '3.1.5'
VERSION = '3.2.0.rc1'

end
end
Expand Down

0 comments on commit 7a18ff1

Please sign in to comment.