Buglette with annotated join condition

Issue #2813 resolved
Jeff Dairiki created an issue

Here's the crux (full test script attached below).

I have an explicitly annotated join condition. One of the columns in the ON clause is not having its name qualified with its table.

    class TableTwo(Base):
        ...
        x = Column(Integer)
        r = relationship(TableOne, primaryjoin=remote(TableOne.x)==foreign(x))

    print sess.query(TableTwo).join('r')
    >>> SELECT ... FROM Two JOIN One ON One.x = x

Executing this query results in an "ambiguous column name" operational error.

This behavior seems to pertain to 0.8.2, rel_0_8 and master (atm).

Specifying the join condition as a string (so that it get evalled later) works correctly.

Using the {{{foreign_keys}}} arg instead of the {{{foreign()}}} annotation work correctly.

Comments (6)

  1. Mike Bayer repo owner

    yeah not sure how to handle this, annotation() makes a copy of the Column, and in this case it's copying it before it gets a table assigned.

    If you use this patch:

    diff --git a/lib/sqlalchemy/orm/relationships.py b/lib/sqlalchemy/orm/relationships.py
    index f37bb8a..7d8a97d 100644
    --- a/lib/sqlalchemy/orm/relationships.py
    +++ b/lib/sqlalchemy/orm/relationships.py
    @@ -1866,6 +1866,8 @@ class JoinCondition(object):
         def _has_annotation(self, clause, annotation):
             for col in visitors.iterate(clause, {}):
                 if annotation in col._annotations:
    +                if col.table is None:
    +                    raise sa_exc.ArgumentError("column %s doesn't have a Table" % col)
                     return True
             else:
    

    it raises an error, but this isn't great.

    the workaround is to use a string, primaryjoin="(remote(TableOne.x) == foreign(TableTwo.x))".

    i might have realized this issue a while back but i dont have a good idea how to deal with it.

  2. Mike Bayer repo owner

    well I guess we can do this, seems to be an approach we're taking already:

    diff --git a/lib/sqlalchemy/sql/elements.py b/lib/sqlalchemy/sql/elements.py
    index 17fb406..4263e82 100644
    --- a/lib/sqlalchemy/sql/elements.py
    +++ b/lib/sqlalchemy/sql/elements.py
    @@ -2355,7 +2355,7 @@ class AnnotatedColumnElement(Annotated):
         def __init__(self, element, values):
             Annotated.__init__(self, element, values)
             ColumnElement.comparator._reset(self)
    -        for attr in ('name', 'key'):
    +        for attr in ('name', 'key', 'table'):
                 if self.__dict__.get(attr, False) is None:
                     self.__dict__.pop(attr)
    
    @@ -2375,6 +2375,10 @@ class AnnotatedColumnElement(Annotated):
             return self._Annotated__element.key
    
         @util.memoized_property
    +    def table(self):
    +        return self._Annotated__element.table
    +
    +    @util.memoized_property
         def info(self):
             return self._Annotated__element.info
    
  3. Jeff Dairiki reporter

    Thanks Mike.

    I've tried the above patch on rel_0_8. It does appear to fix the problem (and the tests pass here too).

    (For the record, here's your patch adapted for rel_0_8. It's on util.py rather than elements.py.)

    diff --git a/lib/sqlalchemy/sql/util.py b/lib/sqlalchemy/sql/util.py
    index 61730f1..2799d8c 100644
    --- a/lib/sqlalchemy/sql/util.py
    +++ b/lib/sqlalchemy/sql/util.py
    @@ -497,7 +497,7 @@ class Annotated(object):
     class AnnotatedColumnElement(Annotated):
         def __init__(self, element, values):
             Annotated.__init__(self, element, values)
    -        for attr in ('name', 'key'):
    +        for attr in ('name', 'key', 'table'):
                 if self.__dict__.get(attr, False) is None:
                     self.__dict__.pop(attr)
    
    @@ -512,6 +512,10 @@ class AnnotatedColumnElement(Annotated):
             return self._Annotated__element.key
    
         @util.memoized_property
    +    def table(self):
    +        return self._Annotated__element.table
    +
    +    @util.memoized_property
         def info(self):
             return self._Annotated__element.info
    
  4. Log in to comment