Skip to content

Commit 8b1c005

Browse files
authored
Fixes before abstract adapter refactor (#1214)
1 parent d726673 commit 8b1c005

File tree

5 files changed

+113
-89
lines changed

5 files changed

+113
-89
lines changed

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ if ENV["RAILS_SOURCE"]
1818
gemspec path: ENV["RAILS_SOURCE"]
1919
elsif ENV["RAILS_BRANCH"]
2020
gem "rails", github: "rails/rails", branch: ENV["RAILS_BRANCH"]
21+
elsif ENV["RAILS_COMMIT"]
22+
gem "rails", github: "rails/rails", ref: ENV["RAILS_COMMIT"]
2123
else
2224
# Need to get rails source because the gem doesn't include tests
2325
version = ENV["RAILS_VERSION"] || begin

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ module CoreExt
1010
module Calculations
1111

1212
private
13-
13+
1414
def build_count_subquery(relation, column_name, distinct)
15-
klass.with_connection do |connection|
15+
model.with_connection do |connection|
1616
relation = relation.unscope(:order) if connection.sqlserver?
1717
super(relation, column_name, distinct)
1818
end

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ module FinderMethods
1111
private
1212

1313
def construct_relation_for_exists(conditions)
14-
klass.with_connection do |connection|
14+
model.with_connection do |connection|
1515
if connection.sqlserver?
1616
_construct_relation_for_exists(conditions)
1717
else

lib/active_record/connection_adapters/sqlserver/schema_statements.rb

Lines changed: 102 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,16 @@ def indexes(table_name)
3737
data = select("EXEC sp_helpindex #{quote(table_name)}", "SCHEMA") rescue []
3838

3939
data.reduce([]) do |indexes, index|
40-
index = index.with_indifferent_access
41-
42-
if index[:index_description].match?(/primary key/)
40+
if index['index_description'].match?(/primary key/)
4341
indexes
4442
else
45-
name = index[:index_name]
46-
unique = index[:index_description].match?(/unique/)
43+
name = index['index_name']
44+
unique = index['index_description'].match?(/unique/)
4745
where = select_value("SELECT [filter_definition] FROM sys.indexes WHERE name = #{quote(name)}", "SCHEMA")
4846
orders = {}
4947
columns = []
5048

51-
index[:index_keys].split(",").each do |column|
49+
index['index_keys'].split(",").each do |column|
5250
column.strip!
5351

5452
if column.end_with?("(-)")
@@ -480,16 +478,15 @@ def initialize_native_database_types
480478
end
481479

482480
def column_definitions(table_name)
483-
identifier = database_prefix_identifier(table_name)
484-
database = identifier.fully_qualified_database_quoted
485-
view_exists = view_exists?(table_name)
486-
view_tblnm = view_table_name(table_name) if view_exists
481+
identifier = database_prefix_identifier(table_name)
482+
database = identifier.fully_qualified_database_quoted
483+
view_exists = view_exists?(table_name)
487484

488485
if view_exists
489486
sql = <<~SQL
490487
SELECT LOWER(c.COLUMN_NAME) AS [name], c.COLUMN_DEFAULT AS [default]
491488
FROM #{database}.INFORMATION_SCHEMA.COLUMNS c
492-
WHERE c.TABLE_NAME = #{quote(view_tblnm)}
489+
WHERE c.TABLE_NAME = #{quote(view_table_name(table_name))}
493490
SQL
494491
results = internal_exec_query(sql, "SCHEMA")
495492
default_functions = results.each.with_object({}) { |row, out| out[row["name"]] = row["default"] }.compact
@@ -498,71 +495,93 @@ def column_definitions(table_name)
498495
sql = column_definitions_sql(database, identifier)
499496

500497
binds = []
501-
nv128 = SQLServer::Type::UnicodeVarchar.new limit: 128
498+
nv128 = SQLServer::Type::UnicodeVarchar.new(limit: 128)
502499
binds << Relation::QueryAttribute.new("TABLE_NAME", identifier.object, nv128)
503500
binds << Relation::QueryAttribute.new("TABLE_SCHEMA", identifier.schema, nv128) unless identifier.schema.blank?
501+
504502
results = internal_exec_query(sql, "SCHEMA", binds)
503+
raise ActiveRecord::StatementInvalid, "Table '#{table_name}' doesn't exist" if results.empty?
505504

506505
columns = results.map do |ci|
507-
ci = ci.symbolize_keys
508-
ci[:_type] = ci[:type]
509-
ci[:table_name] = view_tblnm || table_name
510-
ci[:type] = case ci[:type]
511-
when /^bit|image|text|ntext|datetime$/
512-
ci[:type]
513-
when /^datetime2|datetimeoffset$/i
514-
"#{ci[:type]}(#{ci[:datetime_precision]})"
515-
when /^time$/i
516-
"#{ci[:type]}(#{ci[:datetime_precision]})"
517-
when /^numeric|decimal$/i
518-
"#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
519-
when /^float|real$/i
520-
"#{ci[:type]}"
521-
when /^char|nchar|varchar|nvarchar|binary|varbinary|bigint|int|smallint$/
522-
ci[:length].to_i == -1 ? "#{ci[:type]}(max)" : "#{ci[:type]}(#{ci[:length]})"
523-
else
524-
ci[:type]
525-
end
526-
ci[:default_value],
527-
ci[:default_function] = begin
528-
default = ci[:default_value]
529-
if default.nil? && view_exists
530-
view_column = views_real_column_name(table_name, ci[:name]).downcase
531-
default = default_functions[view_column] if view_column.present?
532-
end
533-
case default
534-
when nil
535-
[nil, nil]
536-
when /\A\((\w+\(\))\)\Z/
537-
default_function = Regexp.last_match[1]
538-
[nil, default_function]
539-
when /\A\(N'(.*)'\)\Z/m
540-
string_literal = SQLServer::Utils.unquote_string(Regexp.last_match[1])
541-
[string_literal, nil]
542-
when /CREATE DEFAULT/mi
543-
[nil, nil]
544-
else
545-
type = case ci[:type]
546-
when /smallint|int|bigint/ then ci[:_type]
547-
else ci[:type]
548-
end
549-
value = default.match(/\A\((.*)\)\Z/m)[1]
550-
value = select_value("SELECT CAST(#{value} AS #{type}) AS value", "SCHEMA")
551-
[value, nil]
552-
end
506+
col = {
507+
name: ci["name"],
508+
numeric_scale: ci["numeric_scale"],
509+
numeric_precision: ci["numeric_precision"],
510+
datetime_precision: ci["datetime_precision"],
511+
collation: ci["collation"],
512+
ordinal_position: ci["ordinal_position"],
513+
length: ci["length"]
514+
}
515+
516+
col[:table_name] = view_table_name(table_name) || table_name
517+
col[:type] = column_type(ci: ci)
518+
col[:default_value], col[:default_function] = default_value_and_function(default: ci['default_value'],
519+
name: ci['name'],
520+
type: col[:type],
521+
original_type: ci['type'],
522+
view_exists: view_exists,
523+
table_name: table_name,
524+
default_functions: default_functions)
525+
526+
col[:null] = ci['is_nullable'].to_i == 1
527+
col[:is_primary] = ci['is_primary'].to_i == 1
528+
529+
if [true, false].include?(ci['is_identity'])
530+
col[:is_identity] = ci['is_identity']
531+
else
532+
col[:is_identity] = ci['is_identity'].to_i == 1
553533
end
554-
ci[:null] = ci[:is_nullable].to_i == 1
555-
ci.delete(:is_nullable)
556-
ci[:is_primary] = ci[:is_primary].to_i == 1
557-
ci[:is_identity] = ci[:is_identity].to_i == 1 unless [TrueClass, FalseClass].include?(ci[:is_identity].class)
558-
ci
534+
535+
col
559536
end
560537

561-
# Since Rails 7, it's expected that all adapter raise error when table doesn't exists.
562-
# I'm not aware of the possibility of tables without columns on SQL Server (postgres have those).
563-
# Raise error if the method return an empty array
564-
columns.tap do |result|
565-
raise ActiveRecord::StatementInvalid, "Table '#{table_name}' doesn't exist" if result.empty?
538+
columns
539+
end
540+
541+
def default_value_and_function(default:, name:, type:, original_type:, view_exists:, table_name:, default_functions:)
542+
if default.nil? && view_exists
543+
view_column = views_real_column_name(table_name, name).downcase
544+
default = default_functions[view_column] if view_column.present?
545+
end
546+
547+
case default
548+
when nil
549+
[nil, nil]
550+
when /\A\((\w+\(\))\)\Z/
551+
default_function = Regexp.last_match[1]
552+
[nil, default_function]
553+
when /\A\(N'(.*)'\)\Z/m
554+
string_literal = SQLServer::Utils.unquote_string(Regexp.last_match[1])
555+
[string_literal, nil]
556+
when /CREATE DEFAULT/mi
557+
[nil, nil]
558+
else
559+
type = case type
560+
when /smallint|int|bigint/ then original_type
561+
else type
562+
end
563+
value = default.match(/\A\((.*)\)\Z/m)[1]
564+
value = select_value("SELECT CAST(#{value} AS #{type}) AS value", "SCHEMA")
565+
[value, nil]
566+
end
567+
end
568+
569+
def column_type(ci:)
570+
case ci['type']
571+
when /^bit|image|text|ntext|datetime$/
572+
ci['type']
573+
when /^datetime2|datetimeoffset$/i
574+
"#{ci['type']}(#{ci['datetime_precision']})"
575+
when /^time$/i
576+
"#{ci['type']}(#{ci['datetime_precision']})"
577+
when /^numeric|decimal$/i
578+
"#{ci['type']}(#{ci['numeric_precision']},#{ci['numeric_scale']})"
579+
when /^float|real$/i
580+
"#{ci['type']}"
581+
when /^char|nchar|varchar|nvarchar|binary|varbinary|bigint|int|smallint$/
582+
ci['length'].to_i == -1 ? "#{ci['type']}(max)" : "#{ci['type']}(#{ci['length']})"
583+
else
584+
ci['type']
566585
end
567586
end
568587

@@ -701,25 +720,26 @@ def lowercase_schema_reflection_sql(node)
701720

702721
def view_table_name(table_name)
703722
view_info = view_information(table_name)
704-
view_info ? get_table_name(view_info["VIEW_DEFINITION"]) : table_name
723+
view_info.present? ? get_table_name(view_info["VIEW_DEFINITION"]) : table_name
705724
end
706725

707726
def view_information(table_name)
708727
@view_information ||= {}
728+
709729
@view_information[table_name] ||= begin
710730
identifier = SQLServer::Utils.extract_identifiers(table_name)
711731
information_query_table = identifier.database.present? ? "[#{identifier.database}].[INFORMATION_SCHEMA].[VIEWS]" : "[INFORMATION_SCHEMA].[VIEWS]"
712-
view_info = select_one "SELECT * FROM #{information_query_table} WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", "SCHEMA"
713-
714-
if view_info
715-
view_info = view_info.with_indifferent_access
716-
if view_info[:VIEW_DEFINITION].blank? || view_info[:VIEW_DEFINITION].length == 4000
717-
view_info[:VIEW_DEFINITION] = begin
718-
select_values("EXEC sp_helptext #{identifier.object_quoted}", "SCHEMA").join
719-
rescue
720-
warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
721-
nil
722-
end
732+
733+
view_info = select_one("SELECT * FROM #{information_query_table} WITH (NOLOCK) WHERE TABLE_NAME = #{quote(identifier.object)}", "SCHEMA").to_h
734+
735+
if view_info.present?
736+
if view_info['VIEW_DEFINITION'].blank? || view_info['VIEW_DEFINITION'].length == 4000
737+
view_info['VIEW_DEFINITION'] = begin
738+
select_values("EXEC sp_helptext #{identifier.object_quoted}", "SCHEMA").join
739+
rescue
740+
warn "No view definition found, possible permissions problem.\nPlease run GRANT VIEW DEFINITION TO your_user;"
741+
nil
742+
end
723743
end
724744
end
725745

@@ -728,8 +748,8 @@ def view_information(table_name)
728748
end
729749

730750
def views_real_column_name(table_name, column_name)
731-
view_definition = view_information(table_name)[:VIEW_DEFINITION]
732-
return column_name unless view_definition
751+
view_definition = view_information(table_name)['VIEW_DEFINITION']
752+
return column_name if view_definition.blank?
733753

734754
# Remove "CREATE VIEW ... AS SELECT ..." and then match the column name.
735755
match_data = view_definition.sub(/CREATE\s+VIEW.*AS\s+SELECT\s/, '').match(/([\w-]*)\s+AS\s+#{column_name}\W/im)

test/cases/coerced_tests.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2419,7 +2419,9 @@ class QueryLogsTest < ActiveRecord::TestCase
24192419
# SQL requires double single-quotes.
24202420
coerce_tests! :test_sql_commenter_format
24212421
def test_sql_commenter_format_coerced
2422-
ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2422+
ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
2423+
ActiveRecord::QueryLogs.tags = [:application]
2424+
24232425
assert_queries_match(%r{/\*application=''active_record''\*/}) do
24242426
Dashboard.first
24252427
end
@@ -2428,7 +2430,7 @@ def test_sql_commenter_format_coerced
24282430
# SQL requires double single-quotes.
24292431
coerce_tests! :test_sqlcommenter_format_value
24302432
def test_sqlcommenter_format_value_coerced
2431-
ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2433+
ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
24322434

24332435
ActiveRecord::QueryLogs.tags = [
24342436
:application,
@@ -2443,7 +2445,7 @@ def test_sqlcommenter_format_value_coerced
24432445
# SQL requires double single-quotes.
24442446
coerce_tests! :test_sqlcommenter_format_value_string_coercible
24452447
def test_sqlcommenter_format_value_string_coercible_coerced
2446-
ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2448+
ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
24472449

24482450
ActiveRecord::QueryLogs.tags = [
24492451
:application,
@@ -2458,7 +2460,7 @@ def test_sqlcommenter_format_value_string_coercible_coerced
24582460
# SQL requires double single-quotes.
24592461
coerce_tests! :test_sqlcommenter_format_allows_string_keys
24602462
def test_sqlcommenter_format_allows_string_keys_coerced
2461-
ActiveRecord::QueryLogs.update_formatter(:sqlcommenter)
2463+
ActiveRecord::QueryLogs.tags_formatter = :sqlcommenter
24622464

24632465
ActiveRecord::QueryLogs.tags = [
24642466
:application,

0 commit comments

Comments
 (0)