tortoise / tortoise-orm

Familiar asyncio ORM for python, built with relations in mind
https://tortoise.github.io
Apache License 2.0
4.68k stars 392 forks source link

Fix for #443 Generates incorrect queries #822

Open MightySCollins opened 3 years ago

MightySCollins commented 3 years ago

Describe the bug From my testing the fix for #443 does not work quite correctly when updating the model. Although the original error is solved it would seem when doing an update query on the model the _id is not appended to the primary key correctly. I assume because no other primary keys need to do this.

To Reproduce

class Order(models.Model):
    id = fields.UUIDField(pk=True, default=uuid.uuid4)

class OrderCustomerDetails(models.Model):
    order: fields.OneToOneRelation[Order] = fields.OneToOneField(
        "models.Order", on_delete=fields.CASCADE, related_name="customer_details", pk=True
    )

Using models similar to above if I was to runt he following code

details = await OrderCustomerDetails.get(order=order)
# Edit some value
await details.save()

The query used would be similar to below:

UPDATE "order_customer_info" SET "name"=$1 WHERE "order"=$2

Expected behavior I believe there needs to be logic to check if its a relation and add on _id when doing the where for an update query so it's like below.

UPDATE "order_customer_info" SET "name"=$1 WHERE "order_id"=$2

I did try and see if I could fix this myself but got lost in the code.

paris-ci commented 3 years ago

Can confirm that this is happening to me too... And I'm not sure where to go in the code to fix the issue :(

paris-ci commented 3 years ago

@long2ice Could you help us again please ? :)

paris-ci commented 3 years ago

Here's some technical information I got while researching the issue

First off, here's the full traceback:

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/tortoise/backends/asyncpg/client.py", line 36, in translate_exceptions_
    return await func(self, *args)
  File "/usr/local/lib/python3.9/site-packages/tortoise/backends/asyncpg/client.py", line 178, in execute_query
    res = await connection.execute(*params)
  File "/usr/local/lib/python3.9/site-packages/asyncpg/connection.py", line 299, in execute
    _, status, _ = await self._execute(
  File "/usr/local/lib/python3.9/site-packages/asyncpg/connection.py", line 1625, in _execute
    result, _ = await self.__execute(
  File "/usr/local/lib/python3.9/site-packages/asyncpg/connection.py", line 1650, in __execute
    return await self._do_execute(
  File "/usr/local/lib/python3.9/site-packages/asyncpg/connection.py", line 1677, in _do_execute
    stmt = await self._get_statement(
  File "/usr/local/lib/python3.9/site-packages/asyncpg/connection.py", line 375, in _get_statement
    statement = await self._protocol.prepare(
  File "asyncpg/protocol/protocol.pyx", line 168, in prepare
asyncpg.exceptions.UndefinedColumnError: column "member" does not exist
HINT:  Perhaps you meant to reference the column "landmines_userdata.member_id".

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/discord/ext/commands/core.py", line 125, in wrapped
    ret = await coro(*args, **kwargs)
  File "/Users/arthur/Desktop/WorkSpace/DHV4/src/cogs/landmines.py", line 125, in place
    await self.landmine(ctx, guild, value, word, message_text=message_text)
  File "/usr/local/lib/python3.9/site-packages/discord/ext/commands/core.py", line 410, in __call__
    return await self.callback(self.cog, *args, **kwargs)
  File "/Users/arthur/Desktop/WorkSpace/DHV4/src/cogs/landmines.py", line 289, in landmine
    await db_data.save()
  File "/usr/local/lib/python3.9/site-packages/tortoise/models.py", line 939, in save
    await executor.execute_update(self, update_fields)
  File "/usr/local/lib/python3.9/site-packages/tortoise/backends/base/executor.py", line 302, in execute_update
    await self.db.execute_query(
  File "/usr/local/lib/python3.9/site-packages/tortoise/backends/asyncpg/client.py", line 38, in translate_exceptions_
    raise OperationalError(exc)
tortoise.exceptions.OperationalError: column "member" does not exist
HINT:  Perhaps you meant to reference the column "landmines_userdata.member_id".

For the following model:

class LandminesUserData(Model):
    landmines_bought: fields.ReverseRelation['LandminesPlaced']
    landmines_stopped: fields.ReverseRelation['LandminesPlaced']

    # This is the problematic part >>>>
    member: fields.ForeignKeyRelation["DiscordMember"] = \
        fields.OneToOneField('models.DiscordMember', related_name='landmines', on_delete=fields.CASCADE, pk=True)
   # <<<<<<<

    # General statistics
    first_played = fields.DatetimeField(auto_now_add=True)
    last_seen = fields.DatetimeField(auto_now_add=True)
    messages_sent = fields.IntField(default=0)
    words_sent = fields.IntField(default=0)
    points_won = fields.IntField(default=0)
    points_recovered = fields.IntField(default=0)
    points_acquired = fields.IntField(default=0)
    points_current = fields.IntField(default=0)
    points_exploded = fields.IntField(default=0)
    points_spent = fields.IntField(default=0)

    # Inventory

    ## Defuse kits
    defuse_kits_bought = fields.IntField(default=0)

    def add_points_for_message(self, message_content):
        words = get_valid_words(message_content)
        words_count = len(words)
        if words_count > 0:
            self.words_sent += words_count
            self.messages_sent += 1
            earned = max(0, int((words_count + random.randint(-1, words_count))))

            if self.points_current <= -10:
                earned *= max(2, int(abs(self.points_current) / 1000) + 2)

            self.points_acquired += earned
            self.points_current += earned
            return True
        else:
            return False

    def __str__(self):
        return f"@{self.member} landmines data"

    class Meta:
        table = 'landmines_userdata'