Skip to content

Conversation

LilyFirefly
Copy link
Contributor

Extracted from #16092 as suggested in #16092 (comment).

@felixxm
Copy link
Member

felixxm commented Sep 26, 2022

@LilyFoote Thanks 👍 Should we update also UTC_TIMESTAMP used in tests to the UTC_TIMESTAMP(6)?

test_now_utc_template = "UTC_TIMESTAMP"

Can we add a regression test? For example by checking microseconds in NowTests.

@LilyFirefly
Copy link
Contributor Author

Should we update also UTC_TIMESTAMP used in tests to the UTC_TIMESTAMP(6)?

I don't think it particularly matters - it's only used in the NowUTC subclass of Now in tests.aggregation.tests. Especially since NowUTC is only used in three tests and they always truncate to the hour or the date.

Thinking about it a bit more, it's weird to me that this is implemented as a feature at all, since it's purely for this one test class.

@LilyFirefly
Copy link
Contributor Author

Can we add a regression test?

This is a bit tricky - the natural test to write checks that the microsecond attribute of the datetime object is greater than 0, but that's technically flaky, since a random datetime could happen to have microsecond set to 0.

My other idea is to make an assertion before we convert the database value to a python datetime object, but I'm not sure how to hook into that location in a test.

@adamchainz
Copy link
Member

This is a bit tricky - the natural test to write checks that the microsecond attribute of the datetime object is greater than 0, but that's technically flaky, since a random datetime could happen to have microsecond set to 0.

1 in a million flaky is almost worse than 1 in 2 flaky 😅

Perhaps you can cast the result to a string and check it has 6 trailing digits of precision?

now_string = Something.objects.annotate(
    now_string=Cast(Now(), TextField())
).get().now_string
self.assertRegex(now_string, "^.*\.\d{6}")

You'd need to check that MySQL doesn't do like Python and not output the digits if it's 0 microseconds... Plus the test may need to be MySQL specific in case other DB's have different string formats.

@LilyFirefly
Copy link
Contributor Author

Plus the test may need to be MySQL specific in case other DB's have different string formats.

It turns out sqlite doesn't do microsecond precision. There's a way to get millisecond precision: (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), but I'm not sure if we want that here.

@LilyFirefly
Copy link
Contributor Author

Thinking about it a bit more, I think we do want millisecond precision on sqlite, since it's better than the status quo.

@LilyFirefly
Copy link
Contributor Author

buildbot, test on oracle.

@adamchainz
Copy link
Member

Thinking about it a bit more, I think we do want millisecond precision on sqlite, since it's better than the status quo.

👍👍👍

@felixxm felixxm changed the title Support microsecond precision in Now on MySQL Fixed #34070 -- Added microseconds support to Now() on SQLite and MySQL. Oct 3, 2022
@felixxm felixxm changed the title Fixed #34070 -- Added microseconds support to Now() on SQLite and MySQL. Fixed #34070 -- Added subsecond support to Now() on SQLite and MySQL. Oct 3, 2022
Copy link
Member

@felixxm felixxm left a comment

Choose a reason for hiding this comment

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

@LilyFoote Thanks 👍

@felixxm felixxm force-pushed the now-microseconds-mysql branch 2 times, most recently from 0489f87 to d37c5bd Compare October 3, 2022 09:50
@felixxm felixxm force-pushed the now-microseconds-mysql branch from d37c5bd to 649b28e Compare October 3, 2022 10:13
@felixxm felixxm merged commit 649b28e into django:main Oct 3, 2022
@LilyFirefly LilyFirefly deleted the now-microseconds-mysql branch October 3, 2022 18:00
return self.as_sql(
compiler,
connection,
template="STRFTIME('%%Y-%%m-%%d %%H:%%M:%%f', 'NOW')",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@felixxm These needed to be %%%%: https://docs.djangoproject.com/en/4.1/ref/models/expressions/#django.db.models.Func.template

I'm looking into writing a standalone test that proves this, but it is currently causing the sqlite tests to break on #16092.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, maybe this is actually a bug on the db defaults side...

Copy link
Member

Choose a reason for hiding this comment

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

I missed that it's documented, however both versions works on SQLite as it has a custom SQLiteCursorWrapper:

FORMAT_QMARK_REGEX = _lazy_re_compile(r"(?<!%)%s")
class SQLiteCursorWrapper(Database.Cursor):
"""
Django uses "format" style placeholders, but pysqlite2 uses "qmark" style.
This fixes it -- but note that if you want to use a literal "%s" in a query,
you'll need to use "%%s".
"""
def execute(self, query, params=None):
if params is None:
return Database.Cursor.execute(self, query)
query = self.convert_query(query)
return Database.Cursor.execute(self, query, params)
def executemany(self, query, param_list):
query = self.convert_query(query)
return Database.Cursor.executemany(self, query, param_list)
def convert_query(self, query):
return FORMAT_QMARK_REGEX.sub("?", query).replace("%%", "%")

and the format string doesn't contain %s. I'm not sure if it's worth changing 🤔

@felixxm felixxm temporarily deployed to schedules October 4, 2022 03:19 Inactive
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants