sqlalchemy: Tables with the same name (in different schemas) doesn't schema-qualifiy or disambiguate the local table name

note to users

a fix here is targeted at 2.0. for a decent engine-level configuration to work around this on PostgreSQL, MySQL/MariaDB, SQLite for 1.4/1.3, see https://github.com/sqlalchemy/sqlalchemy/issues/7471#issuecomment-997207701

Describe the bug

When you have

  • two tables with the same name
  • one of those tables lives in the public schema
  • the other is some other schema
  • and both tables show up in a join on a query

the public one does not qualify itself, and becomes ambiguous in the query

I would ideally want this to qualify the query with public. (either through the dialect’s default schema or i guess perhaps this relates to the search_path), or else for sqlalchemy to realize this is about to happen and internally alias the ambiguous table

To Reproduce

from sqlalchemy import Column, create_engine, ForeignKey, Integer
from sqlalchemy.engine.url import URL
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.orm.session import sessionmaker

Base = declarative_base()


class Foo(Base):
    __tablename__ = "foo"
    __table_args__ = dict(schema="foo")

    id = Column(Integer, primary_key=True)


class Bar(Base):
    __tablename__ = "foo"

    id = Column(ForeignKey("foo.foo.id"), primary_key=True)

    foo = relationship("Foo", uselist=False)


engine = create_engine(
    URL(
        host="localhost",
        drivername="postgresql+psycopg2",
        username="user",
        password="password",
        database="postgres",
        port="5532",
    )
)


engine.execute("drop schema if exists foo cascade")
engine.execute("create schema foo")
Base.metadata.create_all(engine)
Session = sessionmaker()
session = Session(bind=engine)

session.query(Bar).join(Foo).all()

Error

/foo/meow.py:26: SADeprecationWarning: Calling URL() directly is deprecated and will be disabled in a future release.  The public constructor for URL is now the URL.create() method.
  URL(
Traceback (most recent call last):
  File "/foo/.venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1802, in _execute_context
    self.dialect.do_execute(
  File "/foo/.venv/lib/python3.9/site-packages/sqlalchemy/engine/default.py", line 719, in do_execute
    cursor.execute(statement, parameters)
psycopg2.errors.AmbiguousAlias: table reference "foo" is ambiguous
LINE 2: FROM foo JOIN foo.foo ON foo.foo.id = foo.id
                                              ^


The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/foo/meow.py", line 43, in <module>
    session.query(Bar).join(Foo).all()
  File "/foo/.venv/lib/python3.9/site-packages/sqlalchemy/orm/query.py", line 2759, in all
    return self._iter().all()
  File "/foo/.venv/lib/python3.9/site-packages/sqlalchemy/orm/query.py", line 2894, in _iter
    result = self.session.execute(
  File "/foo/.venv/lib/python3.9/site-packages/sqlalchemy/orm/session.py", line 1689, in execute
    result = conn._execute_20(statement, params or {}, execution_options)
  File "/foo/.venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1614, in _execute_20
    return meth(self, args_10style, kwargs_10style, execution_options)
  File "/foo/.venv/lib/python3.9/site-packages/sqlalchemy/sql/elements.py", line 325, in _execute_on_connection
    return connection._execute_clauseelement(
  File "/foo/.venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1481, in _execute_clauseelement
    ret = self._execute_context(
  File "/foo/.venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1845, in _execute_context
    self._handle_dbapi_exception(
  File "/foo/.venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 2026, in _handle_dbapi_exception
    util.raise_(
  File "/foo/.venv/lib/python3.9/site-packages/sqlalchemy/util/compat.py", line 207, in raise_
    raise exception
  File "/foo/.venv/lib/python3.9/site-packages/sqlalchemy/engine/base.py", line 1802, in _execute_context
    self.dialect.do_execute(
  File "/foo/.venv/lib/python3.9/site-packages/sqlalchemy/engine/default.py", line 719, in do_execute
    cursor.execute(statement, parameters)
sqlalchemy.exc.ProgrammingError: (psycopg2.errors.AmbiguousAlias) table reference "foo" is ambiguous
LINE 2: FROM foo JOIN foo.foo ON foo.foo.id = foo.id
                                              ^

[SQL: SELECT foo.id AS foo_id
FROM foo JOIN foo.foo ON foo.foo.id = foo.id]

Versions

  • Python: 3.7+
  • SQLAlchemy: 1.3+
  • Database: postgres
  • DBAPI: psycopg2

Additional context

No response

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 2
  • Comments: 19 (14 by maintainers)

Most upvoted comments

We run pytest-alembic which, for better or worse, asserts that our autogenerate remains empty (plus it’d otherwise be an annoyance we’d have to ignore/remove every time)

it’s gotten as far as a legit pytest plugin now huh. a cautionary tale to anyone who wants to write some code that will “save some typing for developers” 😃

But can confirm that schema_translate_map at least seems to work with the test case, including appeasing alembic’s autogenerate. So that seems like the solution for now, thanks!

ok great. schema translate map has been a tricky feature to get right so let me know if it screws anything up