mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Merge pull request #18067 from kamipo/format_datetime_string_according_to_precision
Format the datetime string according to the precision of the datetime field. Conflicts: activerecord/CHANGELOG.md
This commit is contained in:
commit
acb1991910
6 changed files with 225 additions and 1 deletions
|
@ -1,3 +1,18 @@
|
|||
* Format the datetime string according to the precision of the datetime field.
|
||||
|
||||
Incompatible to rounding behavior between MySQL 5.6 and earlier.
|
||||
|
||||
In 5.5, when you insert `2014-08-17 12:30:00.999999` the fractional part
|
||||
is ignored. In 5.6, it's rounded to `2014-08-17 12:30:01`:
|
||||
|
||||
http://bugs.mysql.com/bug.php?id=68760
|
||||
|
||||
*Ryuta Kamizono*
|
||||
|
||||
* Allow precision option for MySQL datetimes.
|
||||
|
||||
*Ryuta Kamizono*
|
||||
|
||||
* Fixed automatic inverse_of for models nested in module.
|
||||
|
||||
*Andrew McCloud*
|
||||
|
|
|
@ -591,6 +591,13 @@ module ActiveRecord
|
|||
when 0x1000000..0xffffffff; 'longtext'
|
||||
else raise(ActiveRecordError, "No text type has character length #{limit}")
|
||||
end
|
||||
when 'datetime'
|
||||
return super unless precision
|
||||
|
||||
case precision
|
||||
when 0..6; "datetime(#{precision})"
|
||||
else raise(ActiveRecordError, "No datetime type has precision of #{precision}. The allowed range of precision is from 0 to 6.")
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
|
@ -679,6 +686,11 @@ 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
|
||||
|
@ -872,6 +884,22 @@ module ActiveRecord
|
|||
TableDefinition.new(native_database_types, name, temporary, options, as)
|
||||
end
|
||||
|
||||
class MysqlDateTime < Type::DateTime # :nodoc:
|
||||
def type_cast_for_database(value)
|
||||
if value.acts_like?(:time) && value.respond_to?(:usec)
|
||||
result = super.to_s(:db)
|
||||
case precision
|
||||
when 1..6
|
||||
"#{result}.#{sprintf("%0#{precision}d", value.usec / 10 ** (6 - precision))}"
|
||||
else
|
||||
result
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class MysqlString < Type::String # :nodoc:
|
||||
def type_cast_for_database(value)
|
||||
case value
|
||||
|
|
87
activerecord/test/cases/adapters/mysql/datetime_test.rb
Normal file
87
activerecord/test/cases/adapters/mysql/datetime_test.rb
Normal file
|
@ -0,0 +1,87 @@
|
|||
require 'cases/helper'
|
||||
|
||||
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')
|
||||
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
|
||||
ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, precision: 5
|
||||
assert_equal 1, activerecord_column_option('foos', 'created_at', 'precision')
|
||||
assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision')
|
||||
end
|
||||
|
||||
def test_timestamps_helper_with_custom_precision
|
||||
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
|
||||
t.timestamps null: true, precision: 4
|
||||
end
|
||||
assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision')
|
||||
assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision')
|
||||
end
|
||||
|
||||
def test_passing_precision_to_datetime_does_not_set_limit
|
||||
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
|
||||
t.timestamps null: true, precision: 4
|
||||
end
|
||||
assert_nil activerecord_column_option('foos', 'created_at', 'limit')
|
||||
assert_nil activerecord_column_option('foos', 'updated_at', 'limit')
|
||||
end
|
||||
|
||||
def test_invalid_datetime_precision_raises_error
|
||||
assert_raises ActiveRecord::ActiveRecordError do
|
||||
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
|
||||
t.timestamps null: true, precision: 7
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_mysql_agrees_with_activerecord_about_precision
|
||||
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
|
||||
t.timestamps null: true, precision: 4
|
||||
end
|
||||
assert_equal 4, mysql_datetime_precision('foos', 'created_at')
|
||||
assert_equal 4, mysql_datetime_precision('foos', 'updated_at')
|
||||
end
|
||||
|
||||
def test_formatting_datetime_according_to_precision
|
||||
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
|
||||
t.datetime :created_at, precision: 0
|
||||
t.datetime :updated_at, precision: 4
|
||||
end
|
||||
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 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
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mysql_datetime_precision(table_name, column_name)
|
||||
results = ActiveRecord::Base.connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'")
|
||||
result = results.find do |result_hash|
|
||||
result_hash["column_name"] == column_name
|
||||
end
|
||||
result && result["datetime_precision"]
|
||||
end
|
||||
|
||||
def activerecord_column_option(tablename, column_name, option)
|
||||
result = ActiveRecord::Base.connection.columns(tablename).find do |column|
|
||||
column.name == column_name
|
||||
end
|
||||
result && result.send(option)
|
||||
end
|
||||
end
|
||||
end
|
87
activerecord/test/cases/adapters/mysql2/datetime_test.rb
Normal file
87
activerecord/test/cases/adapters/mysql2/datetime_test.rb
Normal file
|
@ -0,0 +1,87 @@
|
|||
require 'cases/helper'
|
||||
|
||||
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')
|
||||
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
|
||||
ActiveRecord::Base.connection.add_column :foos, :updated_at, :datetime, precision: 5
|
||||
assert_equal 1, activerecord_column_option('foos', 'created_at', 'precision')
|
||||
assert_equal 5, activerecord_column_option('foos', 'updated_at', 'precision')
|
||||
end
|
||||
|
||||
def test_timestamps_helper_with_custom_precision
|
||||
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
|
||||
t.timestamps null: true, precision: 4
|
||||
end
|
||||
assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision')
|
||||
assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision')
|
||||
end
|
||||
|
||||
def test_passing_precision_to_datetime_does_not_set_limit
|
||||
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
|
||||
t.timestamps null: true, precision: 4
|
||||
end
|
||||
assert_nil activerecord_column_option('foos', 'created_at', 'limit')
|
||||
assert_nil activerecord_column_option('foos', 'updated_at', 'limit')
|
||||
end
|
||||
|
||||
def test_invalid_datetime_precision_raises_error
|
||||
assert_raises ActiveRecord::ActiveRecordError do
|
||||
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
|
||||
t.timestamps null: true, precision: 7
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_mysql_agrees_with_activerecord_about_precision
|
||||
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
|
||||
t.timestamps null: true, precision: 4
|
||||
end
|
||||
assert_equal 4, mysql_datetime_precision('foos', 'created_at')
|
||||
assert_equal 4, mysql_datetime_precision('foos', 'updated_at')
|
||||
end
|
||||
|
||||
def test_formatting_datetime_according_to_precision
|
||||
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
|
||||
t.datetime :created_at, precision: 0
|
||||
t.datetime :updated_at, precision: 4
|
||||
end
|
||||
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 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
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mysql_datetime_precision(table_name, column_name)
|
||||
results = ActiveRecord::Base.connection.exec_query("SELECT column_name, datetime_precision FROM information_schema.columns WHERE table_name = '#{table_name}'")
|
||||
result = results.find do |result_hash|
|
||||
result_hash["column_name"] == column_name
|
||||
end
|
||||
result && result["datetime_precision"]
|
||||
end
|
||||
|
||||
def activerecord_column_option(tablename, column_name, option)
|
||||
result = ActiveRecord::Base.connection.columns(tablename).find do |column|
|
||||
column.name == column_name
|
||||
end
|
||||
result && result.send(option)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -232,6 +232,13 @@ class SchemaDumperTest < ActiveRecord::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
if mysql_56?
|
||||
def test_schema_dump_includes_datetime_precision
|
||||
output = standard_dump
|
||||
assert_match %r{t.datetime\s+"written_on",\s+precision: 6$}, output
|
||||
end
|
||||
end
|
||||
|
||||
def test_schema_dump_includes_decimal_options
|
||||
output = dump_all_table_schema([/^[^n]/])
|
||||
assert_match %r{precision: 3,[[:space:]]+scale: 2,[[:space:]]+default: 2.78}, output
|
||||
|
|
|
@ -726,7 +726,7 @@ ActiveRecord::Schema.define do
|
|||
t.string :author_name
|
||||
t.string :author_email_address
|
||||
if mysql_56?
|
||||
t.datetime :written_on, limit: 6
|
||||
t.datetime :written_on, precision: 6
|
||||
else
|
||||
t.datetime :written_on
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue