teaching the mysql adapter how to typecast strings returned from the database

This commit is contained in:
Aaron Patterson 2012-07-13 16:58:57 -07:00
parent 0736e169dc
commit d08fee3f28
2 changed files with 134 additions and 4 deletions

View File

@ -298,6 +298,127 @@ module ActiveRecord
@connection.insert_id
end
class Result < ActiveRecord::Result
def initialize(columns, rows, column_types)
super(columns, rows)
@column_types = column_types
end
end
module Fields
class Type
def type; end
def type_cast_for_write(value)
value
end
end
class Identity < Type
def type_cast(value); value; end
end
class Integer < Type
def type_cast(value)
return if value.nil?
value.to_i rescue value ? 1 : 0
end
end
class Date < Type
def type; :date; end
def type_cast(value)
return if value.nil?
# FIXME: probably we can improve this since we know it is mysql
# specific
ConnectionAdapters::Column.value_to_date value
end
end
class DateTime < Type
def type; :datetime; end
def type_cast(value)
return if value.nil?
# FIXME: probably we can improve this since we know it is mysql
# specific
ConnectionAdapters::Column.string_to_time value
end
end
class Time < Type
def type; :time; end
def type_cast(value)
return if value.nil?
# FIXME: probably we can improve this since we know it is mysql
# specific
ConnectionAdapters::Column.string_to_dummy_time value
end
end
class Float < Type
def type; :float; end
def type_cast(value)
return if value.nil?
value.to_f
end
end
class Decimal < Type
def type_cast(value)
return if value.nil?
ConnectionAdapters::Column.value_to_decimal value
end
end
class Boolean < Type
def type_cast(value)
return if value.nil?
ConnectionAdapters::Column.value_to_boolean value
end
end
TYPES = {}
# Register an MySQL +type_id+ with a typcasting object in
# +type+.
def self.register_type(type_id, type)
TYPES[type_id] = type
end
def self.alias_type(new, old)
TYPES[new] = TYPES[old]
end
register_type Mysql::Field::TYPE_TINY, Fields::Boolean.new
register_type Mysql::Field::TYPE_LONG, Fields::Integer.new
alias_type Mysql::Field::TYPE_LONGLONG, Mysql::Field::TYPE_LONG
alias_type Mysql::Field::TYPE_NEWDECIMAL, Mysql::Field::TYPE_LONG
register_type Mysql::Field::TYPE_VAR_STRING, Fields::Identity.new
register_type Mysql::Field::TYPE_BLOB, Fields::Identity.new
register_type Mysql::Field::TYPE_DATE, Fields::Date.new
register_type Mysql::Field::TYPE_DATETIME, Fields::DateTime.new
register_type Mysql::Field::TYPE_TIME, Fields::Time.new
register_type Mysql::Field::TYPE_FLOAT, Fields::Float.new
Mysql::Field.constants.grep(/TYPE/).map { |class_name|
Mysql::Field.const_get class_name
}.reject { |const| TYPES.key? const }.each do |const|
register_type const, Fields::Identity.new
end
end
def exec_without_stmt(sql, name = 'SQL') # :nodoc:
# Some queries, like SHOW CREATE TABLE don't work through the prepared
# statement API. For those queries, we need to use this method. :'(
@ -306,8 +427,17 @@ module ActiveRecord
affected_rows = @connection.affected_rows
if result
cols = result.fetch_fields.map { |field| field.name }
result_set = ActiveRecord::Result.new(cols, result.to_a)
types = {}
result.fetch_fields.each { |field|
if field.decimals > 0
types[field.name] = Fields::Decimal.new
else
types[field.name] = Fields::TYPES.fetch(field.type) {
Fields::Identity.new
}
end
}
result_set = Result.new(types.keys, result.to_a, types)
result.free
else
result_set = ActiveRecord::Result.new([], [])

View File

@ -433,7 +433,7 @@ class CalculationsTest < ActiveRecord::TestCase
Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
# TODO: Investigate why PG isn't being typecast
if current_adapter?(:PostgreSQLAdapter)
if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:MysqlAdapter)
assert_equal "7", Company.includes(:contracts).maximum(:developer_id)
else
assert_equal 7, Company.includes(:contracts).maximum(:developer_id)
@ -444,7 +444,7 @@ class CalculationsTest < ActiveRecord::TestCase
Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])
# TODO: Investigate why PG isn't being typecast
if current_adapter?(:PostgreSQLAdapter)
if current_adapter?(:PostgreSQLAdapter) || current_adapter?(:MysqlAdapter)
assert_equal "7", Company.includes(:contracts).minimum(:developer_id)
else
assert_equal 7, Company.includes(:contracts).minimum(:developer_id)