tortoise / tortoise/tortoise-orm

"OperationalError: ON clause references tables to its right" on self-refencing join

Open
#1,063 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
5.6k
Forks
516
Avg merge
2d 21h
Merged PRs (30d)
9

Description

I don't know if this is a bug or an error on my part: I'm trying to construct a follower-followee relationship between two user-profiles. A profile has a one-to-one relation to a user. Here is a minimal example:

from tortoise.models import Model
from tortoise import Tortoise, fields, run_async

class User(Model):
    id = fields.UUIDField(pk=True)
    username = fields.CharField(max_length=20, unique=True)

class Profile(Model):
    id = fields.IntField(pk=True)
    name = fields.CharField(max_length=255)
    following: fields.ManyToManyRelation["Profile"] = fields.ManyToManyField(
        "models.Profile", related_name="followers", through="followtable",
        backward_key="follower", forward_key="followee"
    )
    followers: fields.ManyToManyRelation["Profile"]
    owner: fields.OneToOneRelation[User] = fields.OneToOneField(
        model_name="models.User", related_name="profile"
    )

async def run():
    await Tortoise.init(db_url="sqlite://:memory:", modules={"models": ["__main__"]})
    await Tortoise.generate_schemas()

    # Set up follower identity: Frank Drebin
    user_drebin = await User.create(username="zucker_fan")
    profile_drebin = await Profile.create(name="Frank Drebin", owner=user_drebin)
    user_drebin_id = user_drebin.id

    # Set up followee identity: Terry Gilliam
    user_gilliam = await User.create(username="monty_fan")
    profile_gilliam = await Profile.create(name="Terry Gilliam", owner=user_gilliam)
    await profile_gilliam.followers.add(profile_drebin)
    await profile_gilliam.save()

    # Now I want to find out who Frank Drebin follows:
    # This works but involves two queries:
    p = await Profile.get(owner_id=user_drebin_id)
    who_drebin_follows = await p.following
    print(who_drebin_follows)

    # This doesn't
    print(await Profile.filter(followers__owner_id = user_drebin_id))

if __name__ == "__main__":
    run_async(run())

The second query should go through the related profiles of the followers and retrieve the ones with the given user id, but it just gets the error:

tortoise.exceptions.OperationalError: ON clause references tables to its right

This is the generated SQL:

SELECT "profile"."name","profile"."owner_id","profile"."id" FROM "profile"
    LEFT OUTER JOIN "followtable" ON "profile2"."id"="followtable"."followee"
    LEFT OUTER JOIN "profile" "profile2" ON "followtable"."follower"="profile2"."id"
WHERE "profile2"."owner_id"='dad3f7d3-095c-41c8-868f-545d4947cf7a';

But I'm looking for a way to kind of turn that statement inside out and fetch the profile2 part like this:

SELECT profile2.* FROM profile
    JOIN followtable ON profile.id = followtable.follower
    JOIN profile profile2 ON profile2.id=followtable.followee
WHERE profile.owner_id = 'dad3f7d3-095c-41c8-868f-545d4947cf7a';

What exactly am I missing in the second query statement?

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the minimal Python example and the SQL generated by Profile.filter(followers__owner_id=user_drebin_id). Compare its join order with the expected SQL and trace how the self-referencing relation is translated. Done means the filter returns the followed profiles without the ON-clause OperationalError.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, sql, sqlite
Domain
databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.