Skip to content

Commit ac617bd

Browse files
committed
Better organization for column_definitions. Test adapter specific tests for these data types to come.
1 parent 0ef3aed commit ac617bd

File tree

2 files changed

+105
-83
lines changed

2 files changed

+105
-83
lines changed

lib/active_record/connection_adapters/sqlserver_adapter.rb

Lines changed: 89 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@ class Base
77

88
def self.sqlserver_connection(config) #:nodoc:
99
require_library_or_gem 'dbi' unless self.class.const_defined?(:DBI)
10-
1110
config = config.symbolize_keys
12-
1311
mode = config[:mode] ? config[:mode].to_s.upcase : 'ADO'
1412
username = config[:username] ? config[:username].to_s : 'sa'
1513
password = config[:password] ? config[:password].to_s : ''
@@ -107,44 +105,23 @@ module ConnectionAdapters
107105

108106
class SQLServerColumn < Column #:nodoc:
109107

110-
attr_reader :identity, :is_special, :is_utf8
111-
112-
def initialize(info)
113-
if info[:type] =~ /numeric|decimal/i
114-
type = "#{info[:type]}(#{info[:numeric_precision]},#{info[:numeric_scale]})"
115-
else
116-
type = "#{info[:type]}(#{info[:length]})"
117-
end
118-
super(info[:name], info[:default_value], type, info[:is_nullable] == 1)
119-
@identity = info[:is_identity]
120-
# TODO: Not sure if these should also be special: varbinary(max), nchar, nvarchar(max)
121-
@is_special = ["text", "ntext", "image"].include?(info[:type])
122-
# Added nchar and nvarchar(max) for unicode types
123-
# http://www.teratrax.com/sql_guide/data_types/sql_server_data_types.html
124-
@is_utf8 = type =~ /nvarchar|ntext|nchar|nvarchar(max)/i
125-
# TODO: check ok to remove @scale = scale_value
126-
@limit = nil unless limitable?(type)
108+
def initialize(name, default, sql_type = nil, null = true, sqlserver_options = {})
109+
super(name, default, sql_type, null)
110+
@sqlserver_options = sqlserver_options
111+
@limit = nil unless limitable?
127112
end
128113

129-
def identity?
130-
@identity
114+
def is_identity?
115+
@sqlserver_options[:is_identity]
131116
end
132117

133-
def limitable?(type)
134-
# SQL Server only supports limits on *char and float types
135-
# although for schema dumping purposes it's useful to know that (big|small)int are 2|8 respectively.
136-
@type == :float || @type == :string || (@type == :integer && type =~ /^(big|small)int/)
118+
def is_special?
119+
# TODO: Not sure if these should be added: varbinary(max), nchar, nvarchar(max)
120+
sql_type =~ /^text|ntext|image$/
137121
end
138-
139-
def simplified_type(field_type)
140-
case field_type
141-
when /real/i then :float
142-
when /money/i then :decimal
143-
when /image/i then :binary
144-
when /bit/i then :boolean
145-
when /uniqueidentifier/i then :string
146-
else super
147-
end
122+
123+
def is_utf8?
124+
sql_type =~ /nvarchar|ntext|nchar|nvarchar(max)/i
148125
end
149126

150127
def type_cast(value)
@@ -168,7 +145,6 @@ def type_cast_code(var_name)
168145
end
169146
end
170147

171-
172148
class << self
173149

174150
def cast_to_datetime(value)
@@ -217,6 +193,25 @@ def new_time(year, mon, mday, hour, min, sec, microsec = 0)
217193

218194
end #class << self
219195

196+
private
197+
198+
def limitable?
199+
# SQL Server only supports limits on *char and float types. Although for schema dumping purposes
200+
# it is useful to know what others are like bigint and smallint. So we trump #extract_limit.
201+
[:float,:string].include?(type) || (type == :integer && sql_type =~ /^(big|small)int/i)
202+
end
203+
204+
def simplified_type(field_type)
205+
case field_type
206+
when /real/i then :float
207+
when /money/i then :decimal
208+
when /image/i then :binary
209+
when /bit/i then :boolean
210+
when /uniqueidentifier/i then :string
211+
else super
212+
end
213+
end
214+
220215
end #SQLServerColumn
221216

222217
# In ADO mode, this adapter will ONLY work on Windows systems,
@@ -582,52 +577,14 @@ def columns(table_name, name = nil)
582577
table_name = table_names[-1]
583578
table_name = table_name.gsub(/[\[\]]/, '')
584579
db_name = "#{table_names[0]}." if table_names.length==3
585-
586-
# COL_LENGTH returns values that do not reflect how much data can be stored in certain data types.
587-
# COL_LENGTH returns -1 for varchar(max), nvarchar(max), and varbinary(max)
588-
# COL_LENGTH returns 16 for ntext, text, image types
589-
# My sessions.data column was varchar(max) and resulted in the following error:
590-
# Your session data is larger than the data column in which it is to be stored. You must increase the size of your data column if you intend to store large data.
591-
sql = %{
592-
SELECT
593-
columns.COLUMN_NAME as name,
594-
columns.DATA_TYPE as type,
595-
CASE
596-
WHEN columns.COLUMN_DEFAULT = '(null)' OR columns.COLUMN_DEFAULT = '(NULL)' THEN NULL
597-
ELSE columns.COLUMN_DEFAULT
598-
END default_value,
599-
columns.NUMERIC_SCALE as numeric_scale,
600-
columns.NUMERIC_PRECISION as numeric_precision,
601-
CASE
602-
WHEN columns.DATA_TYPE IN ('nvarchar') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = -1 THEN 1073741823
603-
WHEN columns.DATA_TYPE IN ('varchar', 'varbinary') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = -1 THEN 2147483647
604-
WHEN columns.DATA_TYPE IN ('ntext') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = 16 THEN 1073741823
605-
WHEN columns.DATA_TYPE IN ('text', 'image') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = 16 THEN 2147483647
606-
ELSE COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME)
607-
END as length,
608-
CASE
609-
WHEN columns.IS_NULLABLE = 'YES' THEN 1
610-
ELSE NULL
611-
end is_nullable,
612-
CASE
613-
WHEN COLUMNPROPERTY(OBJECT_ID(columns.TABLE_NAME), columns.COLUMN_NAME, 'IsIdentity') = 0 THEN NULL
614-
ELSE 1
615-
END is_identity
616-
FROM #{db_name}INFORMATION_SCHEMA.COLUMNS columns
617-
WHERE columns.TABLE_NAME = '#{table_name}'
618-
ORDER BY columns.ordinal_position
619-
}.gsub(/[ \t\r\n]+/,' ')
620-
result = select(sql, name, true)
621-
result.collect do |column_info|
622-
# Remove brackets and outer quotes (if quoted) of default value returned by db, i.e:
623-
# "(1)" => "1", "('1')" => "1", "((-1))" => "-1", "('(-1)')" => "(-1)"
624-
# Unicode strings will be prefixed with an N. Remove that too.
625-
column_info.symbolize_keys!
626-
column_info[:default_value] = column_info[:default_value].match(/\A\(+N?'?(.*?)'?\)+\Z/)[1] if column_info[:default_value]
627-
SQLServerColumn.new(column_info)
580+
column_definitions(table_name,db_name).collect do |ci|
581+
sqlserver_options = ci.except(:name,:default_value,:type,:null)
582+
SQLServerColumn.new ci[:name], ci[:default_value], ci[:type], ci[:null], sqlserver_options
628583
end
629584
end
630585

586+
587+
631588
def rename_table(name, new_name)
632589
execute "EXEC sp_rename '#{name}', '#{new_name}'"
633590
end
@@ -898,13 +855,60 @@ def identity_column(table_name)
898855
@table_columns ||= {}
899856
@table_columns[table_name] = columns(table_name) if @table_columns[table_name] == nil
900857
@table_columns[table_name].each do |col|
901-
return col.name if col.identity?
858+
return col.name if col.is_identity?
902859
end
903860
return nil
904861
end
905862

906863
# SQL UTILITY METHODS ======================================#
907864

865+
def column_definitions(table_name, db_name = nil)
866+
# COL_LENGTH returns values that do not reflect how much data can be stored in certain data types.
867+
# COL_LENGTH returns -1 for varchar(max), nvarchar(max), and varbinary(max)
868+
# COL_LENGTH returns 16 for ntext, text, image types
869+
sql = %{
870+
SELECT
871+
columns.COLUMN_NAME as name,
872+
columns.DATA_TYPE as type,
873+
CASE
874+
WHEN columns.COLUMN_DEFAULT = '(null)' OR columns.COLUMN_DEFAULT = '(NULL)' THEN NULL
875+
ELSE columns.COLUMN_DEFAULT
876+
END as default_value,
877+
columns.NUMERIC_SCALE as numeric_scale,
878+
columns.NUMERIC_PRECISION as numeric_precision,
879+
CASE
880+
WHEN columns.DATA_TYPE IN ('nvarchar') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = -1 THEN 1073741823
881+
WHEN columns.DATA_TYPE IN ('varchar', 'varbinary') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = -1 THEN 2147483647
882+
WHEN columns.DATA_TYPE IN ('ntext') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = 16 THEN 1073741823
883+
WHEN columns.DATA_TYPE IN ('text', 'image') AND COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME) = 16 THEN 2147483647
884+
ELSE COL_LENGTH(columns.TABLE_NAME, columns.COLUMN_NAME)
885+
END as length,
886+
CASE
887+
WHEN columns.IS_NULLABLE = 'YES' THEN 1
888+
ELSE NULL
889+
end as is_nullable,
890+
CASE
891+
WHEN COLUMNPROPERTY(OBJECT_ID(columns.TABLE_NAME), columns.COLUMN_NAME, 'IsIdentity') = 0 THEN NULL
892+
ELSE 1
893+
END as is_identity
894+
FROM #{db_name}INFORMATION_SCHEMA.COLUMNS columns
895+
WHERE columns.TABLE_NAME = '#{table_name}'
896+
ORDER BY columns.ordinal_position
897+
}.gsub(/[ \t\r\n]+/,' ')
898+
results = select(sql,nil,true)
899+
results.collect do |ci|
900+
ci.symbolize_keys!
901+
ci[:type] = if ci[:type] =~ /numeric|decimal/i
902+
"#{ci[:type]}(#{ci[:numeric_precision]},#{ci[:numeric_scale]})"
903+
else
904+
"#{ci[:type]}(#{ci[:length]})"
905+
end
906+
ci[:default_value] = ci[:default_value].match(/\A\(+N?'?(.*?)'?\)+\Z/)[1] if ci[:default_value]
907+
ci[:null] = ci[:is_nullable] == 1 ; ci.delete(:is_nullable)
908+
ci
909+
end
910+
end
911+
908912
def get_table_name(sql)
909913
if sql =~ /^\s*insert\s+into\s+([^\(\s]+)\s*|^\s*update\s+([^\(\s]+)\s*/i
910914
$1 || $2
@@ -915,8 +919,10 @@ def get_table_name(sql)
915919
end
916920
end
917921

922+
923+
918924

919-
925+
920926
def change_order_direction(order)
921927
order.split(",").collect {|fragment|
922928
case fragment
@@ -932,7 +938,7 @@ def get_special_columns(table_name)
932938
@table_columns ||= {}
933939
@table_columns[table_name] ||= columns(table_name)
934940
@table_columns[table_name].each do |col|
935-
special << col.name if col.is_special
941+
special << col.name if col.is_special?
936942
end
937943
special
938944
end
@@ -951,7 +957,7 @@ def get_utf8_columns(table_name)
951957
@table_columns ||= {}
952958
@table_columns[table_name] ||= columns(table_name)
953959
@table_columns[table_name].each do |col|
954-
utf8 << col.name if col.is_utf8
960+
utf8 << col.name if col.is_utf8?
955961
end
956962
utf8
957963
end

test/cases/schema_statement_test_sqlserver.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ class SchemaStatementTestSqlserver < ActiveRecord::TestCase
55
def setup
66
@connection = ActiveRecord::Base.connection
77
end
8+
9+
context 'For #columns' do
10+
11+
# Remove brackets and outer quotes (if quoted) of default value returned by db, i.e:
12+
# "(1)" => "1", "('1')" => "1", "((-1))" => "-1", "('(-1)')" => "(-1)"
13+
# Unicode strings will be prefixed with an N. Remove that too.
14+
15+
# SQL Server only supports limits on *char and float types
16+
# although for schema dumping purposes it's useful to know that (big|small)int are 2|8 respectively.
17+
18+
should 'description' do
19+
20+
end
21+
22+
end
23+
824

925
should 'create integers when no limit supplied' do
1026
assert_equal 'integer', @connection.type_to_sql(:integer)

0 commit comments

Comments
 (0)