Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set precision 6 by default for datetime columns #42297

Merged
merged 1 commit into from Jun 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,9 @@
* Set precision 6 by default for `datetime` columns

By default, datetime columns will have microseconds precision instead of seconds precision.

*Roberto Miranda*

* Allow preloading of associations with instance dependent scopes

*John Hawthorn*, *John Crepezzi*, *Adam Hess*, *Eileen M. Uchitelle*, *Dinah Shi*
Expand Down
Expand Up @@ -411,6 +411,12 @@ def column(name, type, index: nil, **options)
end
end

if @conn.supports_datetime_with_precision?
if type == :datetime && !options.key?(:precision)
options[:precision] = 6
end
end

@columns_hash[name] = new_column_definition(name, type, **options)

if index
Expand Down
Expand Up @@ -613,6 +613,12 @@ def drop_table(table_name, **options)
def add_column(table_name, column_name, type, **options)
return if options[:if_not_exists] == true && column_exists?(table_name, column_name)

if supports_datetime_with_precision?
if type == :datetime && !options.key?(:precision)
options[:precision] = 6
end
end

at = create_alter_table table_name
at.add_column(column_name, type, **options)
execute schema_creation.accept at
Expand Down
13 changes: 13 additions & 0 deletions activerecord/lib/active_record/migration/compatibility.rb
Expand Up @@ -295,6 +295,11 @@ def timestamps(**options)
options[:null] = true if options[:null].nil?
super
end

def column(name, type, index: nil, **options)
options[:precision] ||= nil
super
end
end

def add_reference(table_name, ref_name, **options)
Expand Down Expand Up @@ -324,6 +329,14 @@ def remove_index(table_name, column_name = nil, **options)
super
end

def add_column(table_name, column_name, type, **options)
if type == :datetime
options[:precision] ||= nil
end

super
end

private
def compatible_table_definition(t)
class << t
Expand Down
Expand Up @@ -125,7 +125,7 @@ def test_adds_column_as_custom_type

ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:datetimes_as_enum] = { name: "custom_time_format" }
with_postgresql_datetime_type(:datetimes_as_enum) do
ActiveRecord::Migration.new.add_column :postgresql_timestamp_with_zones, :times, :datetime
ActiveRecord::Migration.new.add_column :postgresql_timestamp_with_zones, :times, :datetime, precision: nil

assert_equal({ "data_type" => "USER-DEFINED", "udt_name" => "custom_time_format" },
PostgresqlTimestampWithZone.connection.execute("select data_type, udt_name from information_schema.columns where column_name = 'times'").to_a.first)
Expand Down
4 changes: 2 additions & 2 deletions activerecord/test/cases/date_time_precision_test.rb
Expand Up @@ -48,8 +48,8 @@ def test_datetime_precision_is_truncated_on_assignment
unless current_adapter?(:Mysql2Adapter)
def test_no_datetime_precision_isnt_truncated_on_assignment
@connection.create_table(:foos, force: true)
@connection.add_column :foos, :created_at, :datetime
@connection.add_column :foos, :updated_at, :datetime, precision: 6
@connection.add_column :foos, :created_at, :datetime, precision: nil
@connection.add_column :foos, :updated_at, :datetime

time = ::Time.now.change(nsec: 123)
foo = Foo.new(created_at: time, updated_at: time)
Expand Down
6 changes: 3 additions & 3 deletions activerecord/test/cases/defaults_test.rb
Expand Up @@ -91,13 +91,13 @@ class PostgresqlDefaultExpressionTest < ActiveRecord::TestCase
output = dump_table_schema("defaults")
if ActiveRecord::Base.connection.database_version >= 100000
assert_match %r/t\.date\s+"modified_date",\s+default: -> { "CURRENT_DATE" }/, output
assert_match %r/t\.datetime\s+"modified_time",\s+default: -> { "CURRENT_TIMESTAMP" }/, output
assert_match %r/t\.datetime\s+"modified_time",\s+precision: 6,\s+default: -> { "CURRENT_TIMESTAMP" }/, output
else
assert_match %r/t\.date\s+"modified_date",\s+default: -> { "\('now'::text\)::date" }/, output
assert_match %r/t\.datetime\s+"modified_time",\s+default: -> { "now\(\)" }/, output
assert_match %r/t\.datetime\s+"modified_time",\s+precision: 6,\s+default: -> { "now\(\)" }/, output
end
assert_match %r/t\.date\s+"modified_date_function",\s+default: -> { "now\(\)" }/, output
assert_match %r/t\.datetime\s+"modified_time_function",\s+default: -> { "now\(\)" }/, output
assert_match %r/t\.datetime\s+"modified_time_function",\s+precision: 6,\s+default: -> { "now\(\)" }/, output
end
end
end
Expand Down
10 changes: 5 additions & 5 deletions activerecord/test/cases/migration/change_schema_test.rb
Expand Up @@ -300,11 +300,11 @@ def test_add_column_with_postgresql_datetime_type
assert_equal :datetime, column.type

if current_adapter?(:PostgreSQLAdapter)
assert_equal "timestamp without time zone", column.sql_type
assert_equal "timestamp(6) without time zone", column.sql_type
elsif current_adapter?(:Mysql2Adapter)
assert_equal "datetime", column.sql_type
assert_equal "datetime(6)", column.sql_type
else
assert_equal connection.type_to_sql("datetime"), column.sql_type
assert_equal connection.type_to_sql("datetime(6)"), column.sql_type
end
end

Expand All @@ -318,7 +318,7 @@ def test_add_column_with_datetime_in_timestamptz_mode
column = connection.columns(:testings).find { |c| c.name == "foo" }

assert_equal :datetime, column.type
assert_equal "timestamp with time zone", column.sql_type
assert_equal "timestamp(6) with time zone", column.sql_type
end
end
end
Expand All @@ -341,7 +341,7 @@ def test_change_column_with_timestamp_type
elsif current_adapter?(:OracleAdapter)
assert_equal "TIMESTAMP(6)", column.sql_type
else
assert_equal connection.type_to_sql("datetime"), column.sql_type
assert_equal connection.type_to_sql("datetime(6)"), column.sql_type
end
end

Expand Down
52 changes: 52 additions & 0 deletions activerecord/test/cases/migration/compatibility_test.rb
Expand Up @@ -335,6 +335,58 @@ def migrate(x)
assert connection.index_exists?(:testings, [:gizmo_type, :gizmo_id], name: :index_testings_on_gizmo_type_and_gizmo_id)
end

def test_datetime_doesnt_set_precision_on_create_table
migration = Class.new(ActiveRecord::Migration[4.2]) {
def migrate(x)
create_table :more_testings do |t|
t.datetime :published_at
end
end
}.new

ActiveRecord::Migrator.new(:up, [migration], @schema_migration).migrate

assert connection.column_exists?(:more_testings, :published_at, **precision_implicit_default)
ensure
connection.drop_table :more_testings rescue nil
end

def test_datetime_doesnt_set_precision_on_change_table
create_migration = Class.new(ActiveRecord::Migration[4.2]) {
def migrate(x)
create_table :more_testings do |t|
t.datetime :published_at
end
end
}.new

change_migration = Class.new(ActiveRecord::Migration[4.2]) {
def migrate(x)
change_table :more_testings do |t|
t.datetime :published_at, default: Time.now
end
end
}.new

ActiveRecord::Migrator.new(:up, [create_migration, change_migration], @schema_migration).migrate

assert connection.column_exists?(:more_testings, :published_at, **precision_implicit_default)
ensure
connection.drop_table :more_testings rescue nil
end

def test_datetime_doesnt_set_precision_on_add_column
migration = Class.new(ActiveRecord::Migration[4.2]) {
def migrate(x)
add_column :testings, :published_at, :datetime, default: Time.now
end
}.new

ActiveRecord::Migrator.new(:up, [migration], @schema_migration).migrate

assert connection.column_exists?(:testings, :published_at, **precision_implicit_default)
end

private
def precision_implicit_default
if current_adapter?(:Mysql2Adapter)
Expand Down
6 changes: 3 additions & 3 deletions activerecord/test/cases/schema_dumper_test.rb
Expand Up @@ -837,7 +837,7 @@ def test_schema_dump_defaults_with_universally_supported_types

assert_match %r{t\.string\s+"string_with_default",.*?default: "Hello!"}, output
assert_match %r{t\.date\s+"date_with_default",\s+default: "2014-06-05"}, output
assert_match %r{t\.datetime\s+"datetime_with_default",\s+default: "2014-06-05 07:17:04"}, output
assert_match %r{t\.datetime\s+"datetime_with_default",\s+precision: 6,\s+default: "2014-06-05 07:17:04"}, output
assert_match %r{t\.time\s+"time_with_default",\s+default: "2000-01-01 07:17:04"}, output
assert_match %r{t\.decimal\s+"decimal_with_default",\s+precision: 20,\s+scale: 10,\s+default: "1234567890.0123456789"}, output
end
Expand All @@ -847,8 +847,8 @@ def test_schema_dump_with_column_infinity_default
output = dump_table_schema("infinity_defaults")
assert_match %r{t\.float\s+"float_with_inf_default",\s+default: ::Float::INFINITY}, output
assert_match %r{t\.float\s+"float_with_nan_default",\s+default: ::Float::NAN}, output
assert_match %r{t\.datetime\s+"beginning_of_time",\s+default: -::Float::INFINITY}, output
assert_match %r{t\.datetime\s+"end_of_time",\s+default: ::Float::INFINITY}, output
assert_match %r{t\.datetime\s+"beginning_of_time",\s+precision: 6,\s+default: -::Float::INFINITY}, output
assert_match %r{t\.datetime\s+"end_of_time",\s+precision: 6,\s+default: ::Float::INFINITY}, output
assert_match %r{t\.date\s+"date_with_neg_inf_default",\s+default: -::Float::INFINITY}, output
assert_match %r{t\.date\s+"date_with_pos_inf_default",\s+default: ::Float::INFINITY}, output
end
Expand Down
6 changes: 3 additions & 3 deletions activerecord/test/schema/mysql2_specific_schema.rb
Expand Up @@ -3,13 +3,13 @@
ActiveRecord::Schema.define do
if supports_datetime_with_precision?
create_table :datetime_defaults, force: true do |t|
t.datetime :modified_datetime, default: -> { "CURRENT_TIMESTAMP" }
t.datetime :precise_datetime, precision: 6, default: -> { "CURRENT_TIMESTAMP(6)" }
t.datetime :modified_datetime, precision: nil, default: -> { "CURRENT_TIMESTAMP" }
t.datetime :precise_datetime, default: -> { "CURRENT_TIMESTAMP(6)" }
end

create_table :timestamp_defaults, force: true do |t|
t.timestamp :nullable_timestamp
t.timestamp :modified_timestamp, default: -> { "CURRENT_TIMESTAMP" }
t.timestamp :modified_timestamp, precision: nil, default: -> { "CURRENT_TIMESTAMP" }
t.timestamp :precise_timestamp, precision: 6, default: -> { "CURRENT_TIMESTAMP(6)" }
end
end
Expand Down