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

TimestampNow generates inconsistant values. #919

Open
HakierGrzonzo opened this issue Jan 15, 2024 · 4 comments
Open

TimestampNow generates inconsistant values. #919

HakierGrzonzo opened this issue Jan 15, 2024 · 4 comments

Comments

@HakierGrzonzo
Copy link

HakierGrzonzo commented Jan 15, 2024

TimestampNow generates values for default Timestamp columns in the ORM code, not in the DB.

As a result, one row with many columns that use TimestampNow as a default value will have inconsistent data.

See this example code:

from piccolo.columns.defaults.timestamp import TimestampNow
from piccolo.engine import SQLiteEngine
from piccolo.table import Table
from piccolo.columns import Timestamp

import asyncio

DB = SQLiteEngine(path="./some_db.sqlite")

class Example(Table, db=DB):
    first = Timestamp(default=TimestampNow())
    second = Timestamp(default=TimestampNow())

async def main():
    await Example.create_table()
    await Example.insert(
        Example()
    )

    data = await Example.select()
    row = data[0]
    print(row)
    assert row[Example.first] == row[Example.second]

asyncio.run(main())

It will fail with the following error:

  File "wherever/test.py", line 23, in main
    assert row[Example.first] == row[Example.second]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError

As the values in the row are:

{
  'id': 1, 
  'first': datetime.datetime(2024, 1, 15, 12, 42, 58, 66463), 
  'second': datetime.datetime(2024, 1, 15, 12, 42, 58, 66467)
}

Any directions how could I fix this bug?

@dantownsend
Copy link
Member

@HakierGrzonzo Yeah, that's an interesting one. I think your best solution is this:

now = datetime.datetime.now(tz=datetime.timezone.utc)

await Example.insert(
    Example(first=now, second=now)
)

We might be able to add a feature to Piccolo where the timestamps only come from the database.

@dantownsend
Copy link
Member

dantownsend commented Jan 15, 2024

If you want the timestamp to be generated by the database instead, this works:

from piccolo.querystring import QueryString

await Example.insert(
    Example(first=QueryString('CURRENT_TIMESTAMP'), second=QueryString('CURRENT_TIMESTAMP')),
)

@HakierGrzonzo
Copy link
Author

@dantownsend

I expanded this solution and came up with this:

from piccolo.columns.defaults.timestamp import TimestampNow
from piccolo.engine import SQLiteEngine
from piccolo.querystring import QueryString
from piccolo.table import Table
from piccolo.columns import Timestamp

import asyncio

DB = SQLiteEngine(path="./some_db.sqlite")


class TimestampNow2(TimestampNow):
    def python(self):
        return QueryString("CURRENT_TIMESTAMP")


class TimestampWithFunctionDefault(Timestamp):
    """
    This is a hack to modify piccolo default parameter type checking.
    """

    def __init__(self, default: TimestampNow2, **kwargs) -> None:
        self._validate_default(default, [TimestampNow2])  # type: ignore

        super().__init__(default=None, **kwargs)
        self.default = default

    @property
    def column_type(self):
        return "TIMESTAMP"


class Example(Table, db=DB):
    first = TimestampWithFunctionDefault(default=TimestampNow2())
    second = TimestampWithFunctionDefault(default=TimestampNow2())


async def main():
    await Example.create_table()
    await Example.insert(Example())

    data = await Example.select()
    row = data[0]
    print(data)
    assert row[Example.first] == row[Example.second]


asyncio.run(main())

Now it passes the assert statement

@dantownsend
Copy link
Member

@HakierGrzonzo That's a nice solution - thanks for sharing!

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

No branches or pull requests

2 participants