Skip to content

Commit

Permalink
teaching the mysql adapter how to typecast strings returned from the …
Browse files Browse the repository at this point in the history
…database
  • Loading branch information
tenderlove committed Jul 13, 2012
1 parent 0736e16 commit d08fee3
Show file tree
Hide file tree
Showing 2 changed files with 134 additions and 4 deletions.
134 changes: 132 additions & 2 deletions activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -298,6 +298,127 @@ def last_inserted_id(result)
@connection.insert_id @connection.insert_id
end 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: def exec_without_stmt(sql, name = 'SQL') # :nodoc:
# Some queries, like SHOW CREATE TABLE don't work through the prepared # Some queries, like SHOW CREATE TABLE don't work through the prepared
# statement API. For those queries, we need to use this method. :'( # statement API. For those queries, we need to use this method. :'(
Expand All @@ -306,8 +427,17 @@ def exec_without_stmt(sql, name = 'SQL') # :nodoc:
affected_rows = @connection.affected_rows affected_rows = @connection.affected_rows


if result if result
cols = result.fetch_fields.map { |field| field.name } types = {}
result_set = ActiveRecord::Result.new(cols, result.to_a) 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 result.free
else else
result_set = ActiveRecord::Result.new([], []) result_set = ActiveRecord::Result.new([], [])
Expand Down
4 changes: 2 additions & 2 deletions activerecord/test/cases/calculations_test.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ def test_maximum_with_not_auto_table_name_prefix_if_column_included
Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])


# TODO: Investigate why PG isn't being typecast # 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) assert_equal "7", Company.includes(:contracts).maximum(:developer_id)
else else
assert_equal 7, Company.includes(:contracts).maximum(:developer_id) assert_equal 7, Company.includes(:contracts).maximum(:developer_id)
Expand All @@ -444,7 +444,7 @@ def test_minimum_with_not_auto_table_name_prefix_if_column_included
Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)])


# TODO: Investigate why PG isn't being typecast # 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) assert_equal "7", Company.includes(:contracts).minimum(:developer_id)
else else
assert_equal 7, Company.includes(:contracts).minimum(:developer_id) assert_equal 7, Company.includes(:contracts).minimum(:developer_id)
Expand Down

0 comments on commit d08fee3

Please sign in to comment.