Add a type object to Column constructor

Part of #15134. In order to perform typecasting polymorphically, we need
to add another argument to the constructor. The order was chosen to
match the `oid_type` on `PostgreSQLColumn`.
This commit is contained in:
Sean Griffin 2014-05-17 11:04:13 -06:00
parent 7359f8190d
commit 4bd5dffc85
14 changed files with 86 additions and 57 deletions

View File

@ -3,6 +3,7 @@ require 'bigdecimal'
require 'bigdecimal/util' require 'bigdecimal/util'
require 'active_support/core_ext/benchmark' require 'active_support/core_ext/benchmark'
require 'active_record/connection_adapters/schema_cache' require 'active_record/connection_adapters/schema_cache'
require 'active_record/connection_adapters/type'
require 'active_record/connection_adapters/abstract/schema_dumper' require 'active_record/connection_adapters/abstract/schema_dumper'
require 'active_record/connection_adapters/abstract/schema_creation' require 'active_record/connection_adapters/abstract/schema_creation'
require 'monitor' require 'monitor'
@ -362,6 +363,10 @@ module ActiveRecord
protected protected
def lookup_cast_type(sql_type) # :nodoc:
Type::Value.new
end
def translate_exception_class(e, sql) def translate_exception_class(e, sql)
message = "#{e.class.name}: #{e.message}: #{sql}" message = "#{e.class.name}: #{e.message}: #{sql}"
@logger.error message if @logger @logger.error message if @logger

View File

@ -56,11 +56,11 @@ module ActiveRecord
class Column < ConnectionAdapters::Column # :nodoc: class Column < ConnectionAdapters::Column # :nodoc:
attr_reader :collation, :strict, :extra attr_reader :collation, :strict, :extra
def initialize(name, default, sql_type = nil, null = true, collation = nil, strict = false, extra = "") def initialize(name, default, cast_type, sql_type = nil, null = true, collation = nil, strict = false, extra = "")
@strict = strict @strict = strict
@collation = collation @collation = collation
@extra = extra @extra = extra
super(name, default, sql_type, null) super(name, default, cast_type, sql_type, null)
end end
def extract_default(default) def extract_default(default)
@ -263,8 +263,9 @@ module ActiveRecord
end end
# Overridden by the adapters to instantiate their specific Column type. # Overridden by the adapters to instantiate their specific Column type.
def new_column(field, default, type, null, collation, extra = "") # :nodoc: def new_column(field, default, sql_type, null, collation, extra = "") # :nodoc:
Column.new(field, default, type, null, collation, extra) cast_type = lookup_cast_type(sql_type)
Column.new(field, default, cast_type, sql_type, null, collation, extra)
end end
# Must return the Mysql error number from the exception, if the exception has an # Must return the Mysql error number from the exception, if the exception has an

View File

@ -22,12 +22,14 @@ module ActiveRecord
# #
# +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>. # +name+ is the column's name, such as <tt>supplier_id</tt> in <tt>supplier_id int(11)</tt>.
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>. # +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
# +cast_type+ is the object used for type casting and type information.
# +sql_type+ is used to extract the column's length, if necessary. For example +60+ in # +sql_type+ is used to extract the column's length, if necessary. For example +60+ in
# <tt>company_name varchar(60)</tt>. # <tt>company_name varchar(60)</tt>.
# It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute. # It will be mapped to one of the standard Rails SQL types in the <tt>type</tt> attribute.
# +null+ determines if this column allows +NULL+ values. # +null+ determines if this column allows +NULL+ values.
def initialize(name, default, sql_type = nil, null = true) def initialize(name, default, cast_type, sql_type = nil, null = true)
@name = name @name = name
@cast_type = cast_type
@sql_type = sql_type @sql_type = sql_type
@null = null @null = null
@limit = extract_limit(sql_type) @limit = extract_limit(sql_type)

View File

@ -69,8 +69,9 @@ module ActiveRecord
end end
end end
def new_column(field, default, type, null, collation, extra = "") # :nodoc: def new_column(field, default, sql_type, null, collation, extra = "") # :nodoc:
Column.new(field, default, type, null, collation, strict_mode?, extra) cast_type = lookup_cast_type(sql_type)
Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
end end
def error_number(exception) def error_number(exception)

View File

@ -156,8 +156,9 @@ module ActiveRecord
end end
end end
def new_column(field, default, type, null, collation, extra = "") # :nodoc: def new_column(field, default, sql_type, null, collation, extra = "") # :nodoc:
Column.new(field, default, type, null, collation, strict_mode?, extra) cast_type = lookup_cast_type(sql_type)
Column.new(field, default, cast_type, sql_type, null, collation, strict_mode?, extra)
end end
def error_number(exception) # :nodoc: def error_number(exception) # :nodoc:

View File

@ -12,10 +12,10 @@ module ActiveRecord
if sql_type =~ /\[\]$/ if sql_type =~ /\[\]$/
@array = true @array = true
super(name, default_value, sql_type[0..sql_type.length - 3], null) super(name, default_value, oid_type, sql_type[0..sql_type.length - 3], null)
else else
@array = false @array = false
super(name, default_value, sql_type, null) super(name, default_value, oid_type, sql_type, null)
end end
@default_function = default if has_default_function?(default_value, default) @default_function = default if has_default_function?(default_value, default)

View File

@ -394,7 +394,9 @@ module ActiveRecord
field["dflt_value"] = $1.gsub('""', '"') field["dflt_value"] = $1.gsub('""', '"')
end end
SQLite3Column.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0) sql_type = field['type']
cast_type = lookup_cast_type(sql_type)
SQLite3Column.new(field['name'], field['dflt_value'], cast_type, sql_type, field['notnull'].to_i == 0)
end end
end end

View File

@ -0,0 +1,8 @@
require 'active_record/connection_adapters/type/value'
module ActiveRecord
module ConnectionAdapters
module Type # :nodoc:
end
end
end

View File

@ -0,0 +1,8 @@
module ActiveRecord
module ConnectionAdapters
module Type
class Value # :nodoc:
end
end
end
end

View File

@ -29,6 +29,7 @@ module ActiveRecord
@columns[table_name] << ActiveRecord::ConnectionAdapters::Column.new( @columns[table_name] << ActiveRecord::ConnectionAdapters::Column.new(
name.to_s, name.to_s,
options[:default], options[:default],
lookup_cast_type(sql_type.to_s),
sql_type.to_s, sql_type.to_s,
options[:null]) options[:null])
end end

View File

@ -9,13 +9,13 @@ module ActiveRecord
end end
def test_type_cast_true def test_type_cast_true
c = Column.new(nil, 1, 'boolean') c = Column.new(nil, 1, Type::Value.new, 'boolean')
assert_equal 1, @conn.type_cast(true, nil) assert_equal 1, @conn.type_cast(true, nil)
assert_equal 1, @conn.type_cast(true, c) assert_equal 1, @conn.type_cast(true, c)
end end
def test_type_cast_false def test_type_cast_false
c = Column.new(nil, 1, 'boolean') c = Column.new(nil, 1, Type::Value.new, 'boolean')
assert_equal 0, @conn.type_cast(false, nil) assert_equal 0, @conn.type_cast(false, nil)
assert_equal 0, @conn.type_cast(false, c) assert_equal 0, @conn.type_cast(false, c)
end end

View File

@ -47,13 +47,13 @@ module ActiveRecord
end end
def test_type_cast_true def test_type_cast_true
c = Column.new(nil, 1, 'int') c = Column.new(nil, 1, Type::Value.new, 'int')
assert_equal 't', @conn.type_cast(true, nil) assert_equal 't', @conn.type_cast(true, nil)
assert_equal 1, @conn.type_cast(true, c) assert_equal 1, @conn.type_cast(true, c)
end end
def test_type_cast_false def test_type_cast_false
c = Column.new(nil, 1, 'int') c = Column.new(nil, 1, Type::Value.new, 'int')
assert_equal 'f', @conn.type_cast(false, nil) assert_equal 'f', @conn.type_cast(false, nil)
assert_equal 0, @conn.type_cast(false, c) assert_equal 0, @conn.type_cast(false, c)
end end
@ -61,16 +61,16 @@ module ActiveRecord
def test_type_cast_string def test_type_cast_string
assert_equal '10', @conn.type_cast('10', nil) assert_equal '10', @conn.type_cast('10', nil)
c = Column.new(nil, 1, 'int') c = Column.new(nil, 1, Type::Value.new, 'int')
assert_equal 10, @conn.type_cast('10', c) assert_equal 10, @conn.type_cast('10', c)
c = Column.new(nil, 1, 'float') c = Column.new(nil, 1, Type::Value.new, 'float')
assert_equal 10.1, @conn.type_cast('10.1', c) assert_equal 10.1, @conn.type_cast('10.1', c)
c = Column.new(nil, 1, 'binary') c = Column.new(nil, 1, Type::Value.new, 'binary')
assert_equal '10.1', @conn.type_cast('10.1', c) assert_equal '10.1', @conn.type_cast('10.1', c)
c = Column.new(nil, 1, 'date') c = Column.new(nil, 1, Type::Value.new, 'date')
assert_equal '10.1', @conn.type_cast('10.1', c) assert_equal '10.1', @conn.type_cast('10.1', c)
end end

View File

@ -12,13 +12,13 @@ module ActiveRecord
end end
def test_can_set_coder def test_can_set_coder
column = Column.new("title", nil, "varchar(20)") column = Column.new("title", nil, Type::Value.new, "varchar(20)")
column.coder = YAML column.coder = YAML
assert_equal YAML, column.coder assert_equal YAML, column.coder
end end
def test_encoded? def test_encoded?
column = Column.new("title", nil, "varchar(20)") column = Column.new("title", nil, Type::Value.new, "varchar(20)")
assert !column.encoded? assert !column.encoded?
column.coder = YAML column.coder = YAML
@ -26,7 +26,7 @@ module ActiveRecord
end end
def test_type_case_coded_column def test_type_case_coded_column
column = Column.new("title", nil, "varchar(20)") column = Column.new("title", nil, Type::Value.new, "varchar(20)")
column.coder = YAML column.coder = YAML
assert_equal "hello", column.type_cast("--- hello") assert_equal "hello", column.type_cast("--- hello")
end end
@ -34,7 +34,7 @@ module ActiveRecord
# Avoid column definitions in create table statements like: # Avoid column definitions in create table statements like:
# `title` varchar(255) DEFAULT NULL # `title` varchar(255) DEFAULT NULL
def test_should_not_include_default_clause_when_default_is_null def test_should_not_include_default_clause_when_default_is_null
column = Column.new("title", nil, "varchar(20)") column = Column.new("title", nil, Type::Value.new, "varchar(20)")
column_def = ColumnDefinition.new( column_def = ColumnDefinition.new(
column.name, "string", column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null) column.limit, column.precision, column.scale, column.default, column.null)
@ -42,7 +42,7 @@ module ActiveRecord
end end
def test_should_include_default_clause_when_default_is_present def test_should_include_default_clause_when_default_is_present
column = Column.new("title", "Hello", "varchar(20)") column = Column.new("title", "Hello", Type::Value.new, "varchar(20)")
column_def = ColumnDefinition.new( column_def = ColumnDefinition.new(
column.name, "string", column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null) column.limit, column.precision, column.scale, column.default, column.null)
@ -50,7 +50,7 @@ module ActiveRecord
end end
def test_should_specify_not_null_if_null_option_is_false def test_should_specify_not_null_if_null_option_is_false
column = Column.new("title", "Hello", "varchar(20)", false) column = Column.new("title", "Hello", Type::Value.new, "varchar(20)", false)
column_def = ColumnDefinition.new( column_def = ColumnDefinition.new(
column.name, "string", column.name, "string",
column.limit, column.precision, column.scale, column.default, column.null) column.limit, column.precision, column.scale, column.default, column.null)
@ -59,68 +59,68 @@ module ActiveRecord
if current_adapter?(:MysqlAdapter) if current_adapter?(:MysqlAdapter)
def test_should_set_default_for_mysql_binary_data_types def test_should_set_default_for_mysql_binary_data_types
binary_column = MysqlAdapter::Column.new("title", "a", "binary(1)") binary_column = MysqlAdapter::Column.new("title", "a", Type::Value.new, "binary(1)")
assert_equal "a", binary_column.default assert_equal "a", binary_column.default
varbinary_column = MysqlAdapter::Column.new("title", "a", "varbinary(1)") varbinary_column = MysqlAdapter::Column.new("title", "a", Type::Value.new, "varbinary(1)")
assert_equal "a", varbinary_column.default assert_equal "a", varbinary_column.default
end end
def test_should_not_set_default_for_blob_and_text_data_types def test_should_not_set_default_for_blob_and_text_data_types
assert_raise ArgumentError do assert_raise ArgumentError do
MysqlAdapter::Column.new("title", "a", "blob") MysqlAdapter::Column.new("title", "a", Type::Value.new, "blob")
end end
assert_raise ArgumentError do assert_raise ArgumentError do
MysqlAdapter::Column.new("title", "Hello", "text") MysqlAdapter::Column.new("title", "Hello", Type::Value.new, "text")
end end
text_column = MysqlAdapter::Column.new("title", nil, "text") text_column = MysqlAdapter::Column.new("title", nil, Type::Value.new, "text")
assert_equal nil, text_column.default assert_equal nil, text_column.default
not_null_text_column = MysqlAdapter::Column.new("title", nil, "text", false) not_null_text_column = MysqlAdapter::Column.new("title", nil, Type::Value.new, "text", false)
assert_equal "", not_null_text_column.default assert_equal "", not_null_text_column.default
end end
def test_has_default_should_return_false_for_blob_and_text_data_types def test_has_default_should_return_false_for_blob_and_text_data_types
blob_column = MysqlAdapter::Column.new("title", nil, "blob") blob_column = MysqlAdapter::Column.new("title", nil, Type::Value.new, "blob")
assert !blob_column.has_default? assert !blob_column.has_default?
text_column = MysqlAdapter::Column.new("title", nil, "text") text_column = MysqlAdapter::Column.new("title", nil, Type::Value.new, "text")
assert !text_column.has_default? assert !text_column.has_default?
end end
end end
if current_adapter?(:Mysql2Adapter) if current_adapter?(:Mysql2Adapter)
def test_should_set_default_for_mysql_binary_data_types def test_should_set_default_for_mysql_binary_data_types
binary_column = Mysql2Adapter::Column.new("title", "a", "binary(1)") binary_column = Mysql2Adapter::Column.new("title", "a", Type::Value.new, "binary(1)")
assert_equal "a", binary_column.default assert_equal "a", binary_column.default
varbinary_column = Mysql2Adapter::Column.new("title", "a", "varbinary(1)") varbinary_column = Mysql2Adapter::Column.new("title", "a", Type::Value.new, "varbinary(1)")
assert_equal "a", varbinary_column.default assert_equal "a", varbinary_column.default
end end
def test_should_not_set_default_for_blob_and_text_data_types def test_should_not_set_default_for_blob_and_text_data_types
assert_raise ArgumentError do assert_raise ArgumentError do
Mysql2Adapter::Column.new("title", "a", "blob") Mysql2Adapter::Column.new("title", "a", Type::Value.new, "blob")
end end
assert_raise ArgumentError do assert_raise ArgumentError do
Mysql2Adapter::Column.new("title", "Hello", "text") Mysql2Adapter::Column.new("title", "Hello", Type::Value.new, "text")
end end
text_column = Mysql2Adapter::Column.new("title", nil, "text") text_column = Mysql2Adapter::Column.new("title", nil, Type::Value.new, "text")
assert_equal nil, text_column.default assert_equal nil, text_column.default
not_null_text_column = Mysql2Adapter::Column.new("title", nil, "text", false) not_null_text_column = Mysql2Adapter::Column.new("title", nil, Type::Value.new, "text", false)
assert_equal "", not_null_text_column.default assert_equal "", not_null_text_column.default
end end
def test_has_default_should_return_false_for_blob_and_text_data_types def test_has_default_should_return_false_for_blob_and_text_data_types
blob_column = Mysql2Adapter::Column.new("title", nil, "blob") blob_column = Mysql2Adapter::Column.new("title", nil, Type::Value.new, "blob")
assert !blob_column.has_default? assert !blob_column.has_default?
text_column = Mysql2Adapter::Column.new("title", nil, "text") text_column = Mysql2Adapter::Column.new("title", nil, Type::Value.new, "text")
assert !text_column.has_default? assert !text_column.has_default?
end end
end end

View File

@ -5,7 +5,7 @@ module ActiveRecord
module ConnectionAdapters module ConnectionAdapters
class ColumnTest < ActiveRecord::TestCase class ColumnTest < ActiveRecord::TestCase
def test_type_cast_boolean def test_type_cast_boolean
column = Column.new("field", nil, "boolean") column = Column.new("field", nil, Type::Value.new, "boolean")
assert column.type_cast('').nil? assert column.type_cast('').nil?
assert column.type_cast(nil).nil? assert column.type_cast(nil).nil?
@ -36,14 +36,14 @@ module ActiveRecord
end end
def test_type_cast_string def test_type_cast_string
column = Column.new("field", nil, "varchar") column = Column.new("field", nil, Type::Value.new, "varchar")
assert_equal "1", column.type_cast(true) assert_equal "1", column.type_cast(true)
assert_equal "0", column.type_cast(false) assert_equal "0", column.type_cast(false)
assert_equal "123", column.type_cast(123) assert_equal "123", column.type_cast(123)
end end
def test_type_cast_integer def test_type_cast_integer
column = Column.new("field", nil, "integer") column = Column.new("field", nil, Type::Value.new, "integer")
assert_equal 1, column.type_cast(1) assert_equal 1, column.type_cast(1)
assert_equal 1, column.type_cast('1') assert_equal 1, column.type_cast('1')
assert_equal 1, column.type_cast('1ignore') assert_equal 1, column.type_cast('1ignore')
@ -56,50 +56,50 @@ module ActiveRecord
end end
def test_type_cast_non_integer_to_integer def test_type_cast_non_integer_to_integer
column = Column.new("field", nil, "integer") column = Column.new("field", nil, Type::Value.new, "integer")
assert_nil column.type_cast([1,2]) assert_nil column.type_cast([1,2])
assert_nil column.type_cast({1 => 2}) assert_nil column.type_cast({1 => 2})
assert_nil column.type_cast((1..2)) assert_nil column.type_cast((1..2))
end end
def test_type_cast_activerecord_to_integer def test_type_cast_activerecord_to_integer
column = Column.new("field", nil, "integer") column = Column.new("field", nil, Type::Value.new, "integer")
firm = Firm.create(:name => 'Apple') firm = Firm.create(:name => 'Apple')
assert_nil column.type_cast(firm) assert_nil column.type_cast(firm)
end end
def test_type_cast_object_without_to_i_to_integer def test_type_cast_object_without_to_i_to_integer
column = Column.new("field", nil, "integer") column = Column.new("field", nil, Type::Value.new, "integer")
assert_nil column.type_cast(Object.new) assert_nil column.type_cast(Object.new)
end end
def test_type_cast_nan_and_infinity_to_integer def test_type_cast_nan_and_infinity_to_integer
column = Column.new("field", nil, "integer") column = Column.new("field", nil, Type::Value.new, "integer")
assert_nil column.type_cast(Float::NAN) assert_nil column.type_cast(Float::NAN)
assert_nil column.type_cast(1.0/0.0) assert_nil column.type_cast(1.0/0.0)
end end
def test_type_cast_float def test_type_cast_float
column = Column.new("field", nil, "float") column = Column.new("field", nil, Type::Value.new, "float")
assert_equal 1.0, column.type_cast("1") assert_equal 1.0, column.type_cast("1")
end end
def test_type_cast_decimal def test_type_cast_decimal
column = Column.new("field", nil, "decimal") column = Column.new("field", nil, Type::Value.new, "decimal")
assert_equal BigDecimal.new("0"), column.type_cast(BigDecimal.new("0")) assert_equal BigDecimal.new("0"), column.type_cast(BigDecimal.new("0"))
assert_equal BigDecimal.new("123"), column.type_cast(123.0) assert_equal BigDecimal.new("123"), column.type_cast(123.0)
assert_equal BigDecimal.new("1"), column.type_cast(:"1") assert_equal BigDecimal.new("1"), column.type_cast(:"1")
end end
def test_type_cast_binary def test_type_cast_binary
column = Column.new("field", nil, "binary") column = Column.new("field", nil, Type::Value.new, "binary")
assert_equal nil, column.type_cast(nil) assert_equal nil, column.type_cast(nil)
assert_equal "1", column.type_cast("1") assert_equal "1", column.type_cast("1")
assert_equal 1, column.type_cast(1) assert_equal 1, column.type_cast(1)
end end
def test_type_cast_time def test_type_cast_time
column = Column.new("field", nil, "time") column = Column.new("field", nil, Type::Value.new, "time")
assert_equal nil, column.type_cast(nil) assert_equal nil, column.type_cast(nil)
assert_equal nil, column.type_cast('') assert_equal nil, column.type_cast('')
assert_equal nil, column.type_cast('ABC') assert_equal nil, column.type_cast('ABC')
@ -109,7 +109,7 @@ module ActiveRecord
end end
def test_type_cast_datetime_and_timestamp def test_type_cast_datetime_and_timestamp
[Column.new("field", nil, "datetime"), Column.new("field", nil, "timestamp")].each do |column| [Column.new("field", nil, Type::Value.new, "datetime"), Column.new("field", nil, Type::Value.new, "timestamp")].each do |column|
assert_equal nil, column.type_cast(nil) assert_equal nil, column.type_cast(nil)
assert_equal nil, column.type_cast('') assert_equal nil, column.type_cast('')
assert_equal nil, column.type_cast(' ') assert_equal nil, column.type_cast(' ')
@ -121,7 +121,7 @@ module ActiveRecord
end end
def test_type_cast_date def test_type_cast_date
column = Column.new("field", nil, "date") column = Column.new("field", nil, Type::Value.new, "date")
assert_equal nil, column.type_cast(nil) assert_equal nil, column.type_cast(nil)
assert_equal nil, column.type_cast('') assert_equal nil, column.type_cast('')
assert_equal nil, column.type_cast(' ') assert_equal nil, column.type_cast(' ')
@ -132,7 +132,7 @@ module ActiveRecord
end end
def test_type_cast_duration_to_integer def test_type_cast_duration_to_integer
column = Column.new("field", nil, "integer") column = Column.new("field", nil, Type::Value.new, "integer")
assert_equal 1800, column.type_cast(30.minutes) assert_equal 1800, column.type_cast(30.minutes)
assert_equal 7200, column.type_cast(2.hours) assert_equal 7200, column.type_cast(2.hours)
end end
@ -147,7 +147,7 @@ module ActiveRecord
if current_adapter?(:SQLite3Adapter) if current_adapter?(:SQLite3Adapter)
def test_binary_encoding def test_binary_encoding
column = SQLite3Column.new("field", nil, "binary") column = SQLite3Column.new("field", nil, Type::Value.new, "binary")
utf8_string = "a string".encode(Encoding::UTF_8) utf8_string = "a string".encode(Encoding::UTF_8)
type_cast = column.type_cast(utf8_string) type_cast = column.type_cast(utf8_string)