Skip to content

Commit 9fefb42

Browse files
committed
Changing the columns metadata method. [tomafro]
Trac bugs 8886, 9469 and 11242 refer to various things that are sorted by this fix (the bulk of which came from a patch on 8886 by tomafro).
1 parent b0ea27b commit 9fefb42

File tree

2 files changed

+79
-39
lines changed

2 files changed

+79
-39
lines changed

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,15 @@ module ConnectionAdapters
5454
class SQLServerColumn < Column# :nodoc:
5555
attr_reader :identity, :is_special
5656

57-
def initialize(name, default, sql_type = nil, identity = false, null = true) # TODO: check ok to remove scale_value = 0
58-
super(name, default, sql_type, null)
59-
@identity = identity
60-
@is_special = sql_type =~ /text|ntext|image/i
57+
def initialize(info)
58+
if info[:type] =~ /numeric|decimal/i
59+
type = "#{info[:type]}(#{info[:numeric_precision]},#{info[:numeric_scale]})"
60+
else
61+
type = "#{info[:type]}(#{info[:length]})"
62+
end
63+
super(info[:name], info[:default_value], type, info[:is_nullable])
64+
@identity = info[:is_identity]
65+
@is_special = ["text", "ntext", "image"].include?(info[:type])
6166
# TODO: check ok to remove @scale = scale_value
6267
# SQL Server only supports limits on *char and float types
6368
@limit = nil unless @type == :float or @type == :string
@@ -102,10 +107,9 @@ def cast_to_time(value)
102107
Time.time_with_datetime_fallback(Base.default_timezone, *time_array) rescue nil
103108
#Time.send(Base.default_timezone, *time_array) rescue nil
104109
end
105-
110+
106111
def cast_to_datetime(value)
107112
return value.to_time if value.is_a?(DBI::Timestamp)
108-
109113
if value.is_a?(Time)
110114
if value.year != 0 and value.month != 0 and value.day != 0
111115
return value
@@ -275,41 +279,52 @@ def select_rows(sql, name = nil)
275279

276280
def columns(table_name, name = nil)
277281
return [] if table_name.blank?
278-
table_name = table_name.to_s if table_name.is_a?(Symbol)
279-
table_name = table_name.split('.')[-1] unless table_name.nil?
282+
table_name = table_name.to_s.split('.')[-1]
280283
table_name = table_name.gsub(/[\[\]]/, '')
281-
sql = %Q{
284+
sql = %{
282285
SELECT
283-
cols.COLUMN_NAME as ColName,
284-
cols.COLUMN_DEFAULT as DefaultValue,
285-
cols.NUMERIC_SCALE as numeric_scale,
286-
cols.NUMERIC_PRECISION as numeric_precision,
287-
cols.DATA_TYPE as ColType,
288-
cols.IS_NULLABLE As IsNullable,
289-
COL_LENGTH(cols.TABLE_NAME, cols.COLUMN_NAME) as Length,
290-
COLUMNPROPERTY(OBJECT_ID(cols.TABLE_NAME), cols.COLUMN_NAME, 'IsIdentity') as IsIdentity,
291-
cols.NUMERIC_SCALE as Scale
292-
FROM INFORMATION_SCHEMA.COLUMNS cols
293-
WHERE cols.TABLE_NAME = '#{table_name}'
286+
columns.COLUMN_NAME as name,
287+
columns.DATA_TYPE as type,
288+
CASE
289+
WHEN columns.COLUMN_DEFAULT = '(null)' OR columns.COLUMN_DEFAULT = '(NULL)' THEN NULL
290+
ELSE columns.COLUMN_DEFAULT
291+
END default_value,
292+
columns.NUMERIC_SCALE as numeric_scale,
293+
columns.NUMERIC_PRECISION as numeric_precision,
294+
COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) as length,
295+
CASE
296+
WHEN constraint_column_usage.constraint_name IS NULL THEN NULL
297+
ELSE 1
298+
END is_primary_key,
299+
CASE
300+
WHEN columns.IS_NULLABLE = 'YES' THEN 1
301+
ELSE NULL
302+
end is_nullable,
303+
CASE
304+
WHEN COLUMNPROPERTY(OBJECT_ID(columns.TABLE_NAME), columns.COLUMN_NAME, 'IsIdentity') = 0 THEN NULL
305+
ELSE 1
306+
END is_identity
307+
FROM INFORMATION_SCHEMA.COLUMNS columns
308+
LEFT OUTER JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS primary_key_constraints ON (
309+
primary_key_constraints.table_name = columns.table_name
310+
AND primary_key_constraints.constraint_type = 'PRIMARY KEY'
311+
)
312+
LEFT OUTER JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE constraint_column_usage ON (
313+
constraint_column_usage.table_name = primary_key_constraints.table_name
314+
AND constraint_column_usage.column_name = columns.column_name
315+
)
316+
WHERE columns.TABLE_NAME = '#{table_name}'
317+
ORDER BY columns.ordinal_position
294318
}
295-
# Comment out if you want to have the Columns select statment logged.
296-
# Personally, I think it adds unnecessary bloat to the log.
297-
# If you do comment it out, make sure to un-comment the "result" line that follows
298-
result = log(sql, name) { @connection.select_all(sql) }
299-
#result = @connection.select_all(sql)
300-
columns = []
301-
result.each do |field|
302-
default = field[:DefaultValue].to_s.gsub!(/[()\']/,"") =~ /null/i ? nil : field[:DefaultValue]
303-
if field[:ColType] =~ /numeric|decimal/i
304-
type = "#{field[:ColType]}(#{field[:numeric_precision]},#{field[:numeric_scale]})"
305-
else
306-
type = "#{field[:ColType]}(#{field[:Length]})"
307-
end
308-
is_identity = field[:IsIdentity] == 1
309-
is_nullable = field[:IsNullable] == 'YES'
310-
columns << SQLServerColumn.new(field[:ColName], default, type, is_identity, is_nullable)
319+
320+
result = select(sql, name, true)
321+
result.collect do |column_info|
322+
# Remove brackets and outer quotes (if quoted) of default value returned by db, i.e:
323+
# "(1)" => "1", "('1')" => "1", "((-1))" => "-1", "('(-1)')" => "(-1)"
324+
column_info.symbolize_keys!
325+
column_info[:default_value] = column_info[:default_value].match(/\A\(+'?(.*?)'?\)+\Z/)[1] if column_info[:default_value]
326+
SQLServerColumn.new(column_info)
311327
end
312-
columns
313328
end
314329

315330
def empty_insert_statement(table_name)
@@ -552,8 +567,9 @@ def __indexes(table_name, name = nil)
552567
indexes
553568
end
554569

555-
def select(sql, name = nil)
556-
repair_special_columns(sql)
570+
571+
def select(sql, name = nil, ignore_special_columns = false)
572+
repair_special_columns(sql) unless ignore_special_columns
557573

558574
result = []
559575
execute(sql) do |handle|

test/cases/adapter_test_sqlserver.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,27 @@ def assert_all_statements_used_are_closed(&block)
9393
GC.enable
9494
end
9595
end
96+
97+
class StringDefaultsTest < ActiveRecord::TestCase
98+
class StringDefaults < ActiveRecord::Base; end;
99+
100+
def test_sqlserver_default_strings_before_save
101+
default = StringDefaults.new
102+
assert_equal nil, default.string_with_null_default
103+
assert_equal 'null', default.string_with_pretend_null_one
104+
assert_equal '(null)', default.string_with_pretend_null_two
105+
assert_equal 'NULL', default.string_with_pretend_null_three
106+
assert_equal '(NULL)', default.string_with_pretend_null_four
107+
end
108+
109+
def test_sqlserver_default_strings_after_save
110+
default = StringDefaults.create
111+
assert_equal nil, default.string_with_null_default
112+
assert_equal 'null', default.string_with_pretend_null_one
113+
assert_equal '(null)', default.string_with_pretend_null_two
114+
assert_equal 'NULL', default.string_with_pretend_null_three
115+
assert_equal '(NULL)', default.string_with_pretend_null_four
116+
end
117+
118+
119+
end

0 commit comments

Comments
 (0)