1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Refactor microsecond precision to be database agnostic

The various databases don't actually need significantly different
handling for this behavior, and they can achieve it without knowing
about the type of the object.

The old implementation was returning a string, which will cause problems
such as breaking TZ aware attributes, and making it impossible for the
adapters to supply their logic for time objects.
This commit is contained in:
Sean Griffin 2015-02-10 11:52:01 -07:00
parent f926c1c953
commit f1a0fa9e19
11 changed files with 55 additions and 84 deletions

View file

@ -127,7 +127,12 @@ module ActiveRecord
end
end
value.to_s(:db)
result = value.to_s(:db)
if value.respond_to?(:usec) && value.usec > 0
"#{result}.#{sprintf("%06d", value.usec)}"
else
result
end
end
def prepare_binds_for_database(binds) # :nodoc:

View file

@ -29,7 +29,7 @@ module ActiveRecord
limit = column.limit || native_database_types[column.type][:limit]
spec[:limit] = limit.inspect if limit
spec[:precision] = column.precision.inspect if column.precision
spec[:precision] = column.precision.inspect if column.precision && column.precision != 0
spec[:scale] = column.scale.inspect if column.scale
default = schema_default(column) if column.has_default?

View file

@ -263,18 +263,6 @@ module ActiveRecord
{}
end
# QUOTING ==================================================
# Quote date/time values for use in SQL input. Includes microseconds
# if the value is a Time responding to usec.
def quoted_date(value) #:nodoc:
if value.acts_like?(:time) && value.respond_to?(:usec)
"#{super}.#{sprintf("%06d", value.usec)}"
else
super
end
end
# Returns a bind substitution value given a bind +column+
# NOTE: The column param is currently being used by the sqlserver-adapter
def substitute_at(column, _unused = 0)
@ -409,15 +397,15 @@ module ActiveRecord
protected
def initialize_type_map(m) # :nodoc:
register_class_with_limit m, %r(boolean)i, Type::Boolean
register_class_with_limit m, %r(char)i, Type::String
register_class_with_limit m, %r(binary)i, Type::Binary
register_class_with_limit m, %r(text)i, Type::Text
register_class_with_limit m, %r(date)i, Type::Date
register_class_with_limit m, %r(time)i, Type::Time
register_class_with_limit m, %r(datetime)i, Type::DateTime
register_class_with_limit m, %r(float)i, Type::Float
register_class_with_limit m, %r(int)i, Type::Integer
register_class_with_limit m, %r(boolean)i, Type::Boolean
register_class_with_limit m, %r(char)i, Type::String
register_class_with_limit m, %r(binary)i, Type::Binary
register_class_with_limit m, %r(text)i, Type::Text
register_class_with_precision m, %r(date)i, Type::Date
register_class_with_precision m, %r(time)i, Type::Time
register_class_with_precision m, %r(datetime)i, Type::DateTime
register_class_with_limit m, %r(float)i, Type::Float
register_class_with_limit m, %r(int)i, Type::Integer
m.alias_type %r(blob)i, 'binary'
m.alias_type %r(clob)i, 'text'
@ -451,6 +439,13 @@ module ActiveRecord
end
end
def register_class_with_precision(mapping, key, klass) # :nodoc:
mapping.register_type(key) do |*args|
precision = extract_precision(args.last)
klass.new(precision: precision)
end
end
def extract_scale(sql_type) # :nodoc:
case sql_type
when /\((\d+)\)/ then 0

View file

@ -712,11 +712,6 @@ module ActiveRecord
m.alias_type %r(year)i, 'integer'
m.alias_type %r(bit)i, 'binary'
m.register_type(%r(datetime)i) do |sql_type|
precision = extract_precision(sql_type)
MysqlDateTime.new(precision: precision)
end
m.register_type(%r(enum)i) do |sql_type|
limit = sql_type[/^enum\((.+)\)/i, 1]
.split(',').map{|enum| enum.strip.length - 2}.max
@ -734,6 +729,14 @@ module ActiveRecord
end
end
def extract_precision(sql_type)
if /datetime/ === sql_type
super || 0
else
super
end
end
def fetch_type_metadata(sql_type, collation = "", extra = "")
MysqlTypeMetadata.new(super(sql_type), collation: collation, extra: extra, strict: strict_mode?)
end
@ -916,14 +919,6 @@ module ActiveRecord
TableDefinition.new(native_database_types, name, temporary, options, as)
end
class MysqlDateTime < Type::DateTime # :nodoc:
private
def has_precision?
precision || 0
end
end
class MysqlString < Type::String # :nodoc:
def type_cast_for_database(value)
case value
@ -945,7 +940,7 @@ module ActiveRecord
end
def type_classes_with_standard_constructor
super.merge(string: MysqlString, date_time: MysqlDateTime)
super.merge(string: MysqlString)
end
end
end

View file

@ -5,15 +5,6 @@ module ActiveRecord
class DateTime < Type::DateTime # :nodoc:
include Infinity
def type_cast_for_database(value)
if has_precision? && value.acts_like?(:time) && value.year <= 0
bce_year = format("%04d", -value.year + 1)
super.sub(/^-?\d+/, bce_year) + " BC"
else
super
end
end
def cast_value(value)
if value.is_a?(::String)
case value

View file

@ -11,28 +11,25 @@ module ActiveRecord
end
def type_cast_for_database(value)
return super unless value.acts_like?(:time)
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
if value.respond_to?(zone_conversion_method)
value = value.send(zone_conversion_method)
if precision && value.respond_to?(:usec)
number_of_insignificant_digits = 6 - precision
round_power = 10 ** number_of_insignificant_digits
value = value.change(usec: value.usec / round_power * round_power)
end
return value unless has_precision?
if value.acts_like?(:time)
zone_conversion_method = ActiveRecord::Base.default_timezone == :utc ? :getutc : :getlocal
result = value.to_s(:db)
if value.respond_to?(:usec) && (1..6).cover?(precision)
"#{result}.#{sprintf("%0#{precision}d", value.usec / 10 ** (6 - precision))}"
else
result
if value.respond_to?(zone_conversion_method)
value = value.send(zone_conversion_method)
end
end
value
end
private
alias has_precision? precision
def cast_value(string)
return string unless string.is_a?(::String)
return if string.empty?

View file

@ -122,11 +122,4 @@ class MysqlConnectionTest < ActiveRecord::TestCase
ensure
@connection.execute "DROP TABLE `bar_baz`"
end
if mysql_56?
def test_quote_time_usec
assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0))
assert_equal "'1970-01-01 00:00:00.000000'", @connection.quote(Time.at(0).to_datetime)
end
end
end

View file

@ -4,15 +4,12 @@ if mysql_56?
class DateTimeTest < ActiveRecord::TestCase
self.use_transactional_fixtures = false
class Foo < ActiveRecord::Base; end
def test_default_datetime_precision
ActiveRecord::Base.connection.create_table(:foos, force: true)
ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime
ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime
assert_nil activerecord_column_option('foos', 'created_at', 'precision')
teardown do
ActiveRecord::Base.connection.drop_table(:foos, if_exists: true)
end
class Foo < ActiveRecord::Base; end
def test_datetime_data_type_with_precision
ActiveRecord::Base.connection.create_table(:foos, force: true)
ActiveRecord::Base.connection.add_column :foos, :created_at, :datetime, precision: 1

View file

@ -27,11 +27,6 @@ module ActiveRecord
assert_equal "'Infinity'", @conn.quote(infinity)
end
def test_quote_time_usec
assert_equal "'1970-01-01 00:00:00.000000'", @conn.quote(Time.at(0))
assert_equal "'1970-01-01 00:00:00.000000'", @conn.quote(Time.at(0).to_datetime)
end
def test_quote_range
range = "1,2]'; SELECT * FROM users; --".."a"
type = OID::Range.new(Type::Integer.new, :int8range)

View file

@ -145,10 +145,13 @@ class TimestampTest < ActiveRecord::TestCase
date = ::Time.utc(2014, 8, 17, 12, 30, 0, 999999)
Foo.create!(created_at: date, updated_at: date)
assert foo = Foo.find_by(created_at: date)
assert_equal 1, Foo.where(updated_at: date).count
assert_equal date.to_s, foo.created_at.to_s
assert_equal date.to_s, foo.updated_at.to_s
assert_equal 000000, foo.created_at.usec
assert_equal 999900, foo.updated_at.usec
ensure
ActiveRecord::Base.connection.drop_table(:foos, if_exists: true)
end
private

View file

@ -46,28 +46,28 @@ module ActiveRecord
def test_quoted_time_utc
with_timezone_config default: :utc do
t = Time.now
t = Time.now.change(usec: 0)
assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t)
end
end
def test_quoted_time_local
with_timezone_config default: :local do
t = Time.now
t = Time.now.change(usec: 0)
assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t)
end
end
def test_quoted_time_crazy
with_timezone_config default: :asdfasdf do
t = Time.now
t = Time.now.change(usec: 0)
assert_equal t.getlocal.to_s(:db), @quoter.quoted_date(t)
end
end
def test_quoted_datetime_utc
with_timezone_config default: :utc do
t = DateTime.now
t = Time.now.change(usec: 0).to_datetime
assert_equal t.getutc.to_s(:db), @quoter.quoted_date(t)
end
end
@ -76,7 +76,7 @@ module ActiveRecord
# DateTime doesn't define getlocal, so make sure it does nothing
def test_quoted_datetime_local
with_timezone_config default: :local do
t = DateTime.now
t = Time.now.change(usec: 0).to_datetime
assert_equal t.to_s(:db), @quoter.quoted_date(t)
end
end