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

AR: take precision into count when assigning a value to timestamp attribute #20317

Merged
merged 1 commit into from
Sep 23, 2015

Conversation

bogdan
Copy link
Contributor

@bogdan bogdan commented May 27, 2015

The Problem

Consider this following on MySQL (that have 0 precision on datetime by default):

d1 = Developer.create!(name: 'bogdan')
d2 = Developer.find(d1.id)
d1.created_at.usec # => 837728
d2.created_at.usec # => 0
# It results in a madness:
d1.created_at == d2.created_at # => false
d1.cache_key == d2.cache_key # => false

Rails 4.1.10 and master are affected. Not sure about others.

Timestamp column can have less precision than ruby timestamp
In result in how big a fraction of a second can be stored in the
database.

If the precision is low enough, (mysql default is 0 so it is always low
enough by default) the value changes when model is reloaded from the
database.

The Patch

In order to fix that we just use a precision of a column when assigning a value to the timestamp attribute as time object (not as string - that seems working).

Fixing tests

When I've applied a patch, the new problem raised: existing tests are written incorrectly for mysql that have 0 precision by default.
There are many tests that check that updated_at changes on particular model. But it is only true for ruby level but not in the database. This patch reveals the problem.

In order to make them pass I've set the precision for tables that are used to check if updated_at is changing big enough and consistent for all databases.

Further problems

datetime column type works differently in different databases now because if different default precision. It results in different behaviour of the same AR schema on different databases.

It should be consistent. It can be done by setting up a precision option of add_column to some value all the time and disallowing it to be nil.

@rafaelfranca
Copy link
Member

Tests are broken because datetime precision is not supported at the version of mysql that we have in our servers. This make me wonder, would this patch work on old MySQL versions?

@bogdan
Copy link
Contributor Author

bogdan commented May 27, 2015

@rafaelfranca yes it will: at the rails level, unsupported precision works like precision # => 0. It means that all seconds fractions will be removed.

The fact that older mysql don't support precision raises the following problem:

  def test_cache_key_changes_when_child_touched
    owner = owners(:blackbeard)
    pet   = pets(:parrot)

    owner.update_column :updated_at, Time.current
    key = owner.cache_key

    assert pet.touch
    assert_not_equal key, owner.reload.cache_key
  end

It will only work stable if there will be at least 1 second between the creation of owner and touch of pet. It can be solved by putting sleep(1) there or by marking as pending on old versions of mysql.

I've put sleep(1) initially but it didn't work because too many tests would require it and they end up being very slow.

I'll think more how we can solve it. If you have an idea, please share with me.

@rafaelfranca
Copy link
Member

cc @sgrif

@bogdan
Copy link
Contributor Author

bogdan commented Jun 13, 2015

Hope I've fixed the build on mysql5.6: I made it differently with travel so that tests don't relay on ruby-is-slow.

Can someone advice how to go from PR to corresponding CI build? Find it here: https://travis-ci.org/rails/rails/pull_requests manually seems impossible.

@bogdan
Copy link
Contributor Author

bogdan commented Jul 15, 2015

@sgrif @rafaelfranca I understand the work load of rails core team, but this is regression problem from 4.0 to 4.1 and we kind of expect a feedback sooner than later. Can you please merge this in and backport to 4.1 or point out why it can not be done?

@sgrif
Copy link
Contributor

sgrif commented Jul 15, 2015

Forcing a Travis build.

@sgrif sgrif closed this Jul 15, 2015
@sgrif sgrif reopened this Jul 15, 2015
@sgrif
Copy link
Contributor

sgrif commented Jul 15, 2015

I'm traveling and don't have my computer right now but I'll take care of this next chance I get.

@bogdan
Copy link
Contributor Author

bogdan commented Sep 21, 2015

@sgrif if you have back to work, it would be cool to receive another review here

@sgrif
Copy link
Contributor

sgrif commented Sep 21, 2015

Are you Github stalking me? :trollface: Taking a look now.

in_instance = d.created_at
from_db = Parrot.find(d.id).created_at
assert_equal from_db.usec, in_instance.usec
assert_equal from_db, in_instance
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this test hard to follow, and I don't think this is relevant to the behavior of AR::Timestamp. Can we move this to a separate test in something like test/cases/type/date_time_test.rb, and have it look something like this?

model = Model.new(date_time_attr_that_isnt_auto_touched: Time.now)
assert_equal model.date_time_attr, model.tap(&:save).reload.date_time_attr

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any test like that inside am/test/cases/type/ that uses Model. They all just instantiate the AM::Type::* object and call #cast method there.

Also, testing this feature in AM will not cover this change:
https://github.com/rails/rails/pull/20317/files#diff-2695559f74d901580d9c9314a9f4aff7R14
that caused postgresql specific bug.

I would stick with test we have know. I will only change it with use of #reload.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it uses a model, create a test in the active record namespace.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are already several tests of this nature in activerecord/test/cases/type

@sgrif
Copy link
Contributor

sgrif commented Sep 21, 2015

A few nitpicks. This will need to be rebased. The relevant classes have been moved to Active Model.

… attribute

Timestamp column can have less precision than ruby timestamp
In result in how big a fraction of a second can be stored in the
database.

  m = Model.create!
  m.created_at.usec == m.reload.created_at.usec
    # => false
    # due to different seconds precision in Time.now and database column

If the precision is low enough, (mysql default is 0, so it is always low
enough by default) the value changes when model is reloaded from the
database. This patch fixes that issue ensuring that any timestamp
assigned as an attribute is converted to column precision under the
attribute.
@bogdan
Copy link
Contributor Author

bogdan commented Sep 23, 2015

You are right about that test. I didn't get what you meant from the first try.

So:

  • Renamed method to apply_seconds_precision. Feel like it is the best.
  • Reworked test to be in proper place and don't depend on autoupdated time stamps.

@sgrif sgrif merged commit d03f519 into rails:master Sep 23, 2015
sgrif added a commit that referenced this pull request Sep 23, 2015
AR: take precision into count when assigning a value to timestamp
attribute
sgrif added a commit that referenced this pull request Sep 23, 2015
Specifically, versions of MySQL prior to 5.6 do not support this, which
is what's used on Travis by default. The method `mysql_56?` appeared to
only ever be used to conditionally apply subsecond precision, so I've
generalized it and used it more liberally.

This should fix the test failures caused by #20317
sgrif added a commit that referenced this pull request Sep 23, 2015
When I originally reviewed the #20317, I believe these changes were
present, but it appears that it was later updated so that they were
removed. Since Travis hadn't re-run the build, this slipped through.
sgrif added a commit to sgrif/rails that referenced this pull request Jul 25, 2016
The patch does not apply at all cleanly, so this is a manual backport of
the most relevant bits
sgrif added a commit to sgrif/rails that referenced this pull request Jul 25, 2016
Specifically, versions of MySQL prior to 5.6 do not support this, which
is what's used on Travis by default. The method `mysql_56?` appeared to
only ever be used to conditionally apply subsecond precision, so I've
generalized it and used it more liberally.

This should fix the test failures caused by rails#20317
pixeltrix added a commit that referenced this pull request Mar 11, 2018
In #20317, datetime columns had their precision applied on assignment but
that behaviour wasn't applied to time columns - this commit fixes that.

Fixes #30301.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants