From: Mike Bayer Date: Thu, 23 Jan 2014 19:49:04 +0000 (-0500) Subject: - Fixed an 0.9 regression where the automatic aliasing applied by X-Git-Tag: rel_0_9_2~32 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=15b23c7f71c0ca8c526db2794b2c459c84875ab3;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Fixed an 0.9 regression where the automatic aliasing applied by :class:`.Query` and in other situations where selects or joins were aliased (such as joined table inheritance) could fail if a user-defined :class:`.Column` subclass were used in the expression. In this case, the subclass would fail to propagate ORM-specific "annotations" along needed by the adaptation. The "expression annotations" system has been corrected to account for this case. [ticket:2918] --- diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst index 893e3970fe..a321f27472 100644 --- a/doc/build/changelog/changelog_09.rst +++ b/doc/build/changelog/changelog_09.rst @@ -14,6 +14,18 @@ .. changelog:: :version: 0.9.2 + .. change:: + :tags: bug, orm + :tickets: 2918 + + Fixed an 0.9 regression where the automatic aliasing applied by + :class:`.Query` and in other situations where selects or joins + were aliased (such as joined table inheritance) could fail if a + user-defined :class:`.Column` subclass were used in the expression. + In this case, the subclass would fail to propagate ORM-specific + "annotations" along needed by the adaptation. The "expression + annotations" system has been corrected to account for this case. + .. change:: :tags: feature, orm diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 6bd465e9c7..23fe9315a5 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -229,7 +229,6 @@ class Query(object): have been applied within this query.""" adapters = [] - # do we adapt all expression elements or only those # tagged as 'ORM' constructs ? orm_only = getattr(self, '_orm_only_adapt', orm_only) diff --git a/lib/sqlalchemy/sql/annotation.py b/lib/sqlalchemy/sql/annotation.py index b5b7849d21..11b0666750 100644 --- a/lib/sqlalchemy/sql/annotation.py +++ b/lib/sqlalchemy/sql/annotation.py @@ -167,9 +167,18 @@ def _new_annotation_type(cls, base_cls): return cls elif cls in annotated_classes: return annotated_classes[cls] + + for super_ in cls.__mro__: + # check if an Annotated subclass more specific than + # the given base_cls is already registered, such + # as AnnotatedColumnElement. + if super_ in annotated_classes: + base_cls = annotated_classes[super_] + break + annotated_classes[cls] = anno_cls = type( - "Annotated%s" % cls.__name__, - (base_cls, cls), {}) + "Annotated%s" % cls.__name__, + (base_cls, cls), {}) globals()["Annotated%s" % cls.__name__] = anno_cls return anno_cls diff --git a/test/orm/inheritance/test_assorted_poly.py b/test/orm/inheritance/test_assorted_poly.py index da0e3b1a3c..c3ed73c9ca 100644 --- a/test/orm/inheritance/test_assorted_poly.py +++ b/test/orm/inheritance/test_assorted_poly.py @@ -1521,3 +1521,39 @@ class Ticket2419Test(fixtures.DeclarativeMappedTest): q.all(), [(b, True)] ) + +class ColSubclassTest(fixtures.DeclarativeMappedTest, testing.AssertsCompiledSQL): + """Test [ticket:2918]'s test case.""" + + run_create_tables = None + __dialect__ = 'default' + + @classmethod + def setup_classes(cls): + from sqlalchemy.schema import Column + Base = cls.DeclarativeBasic + + class A(Base): + __tablename__ = 'a' + + id = Column(Integer, primary_key=True) + + class MySpecialColumn(Column): + pass + + class B(A): + __tablename__ = 'b' + + id = Column(ForeignKey('a.id'), primary_key=True) + x = MySpecialColumn(String) + + def test_polymorphic_adaptation(self): + A, B = self.classes.A, self.classes.B + + s = Session() + self.assert_compile( + s.query(A).join(B).filter(B.x == 'test'), + "SELECT a.id AS a_id FROM a JOIN " + "(a AS a_1 JOIN b AS b_1 ON a_1.id = b_1.id) " + "ON a.id = b_1.id WHERE b_1.x = :x_1" + ) diff --git a/test/sql/test_selectable.py b/test/sql/test_selectable.py index 8c7bf43b04..3c29d9784e 100644 --- a/test/sql/test_selectable.py +++ b/test/sql/test_selectable.py @@ -1497,6 +1497,29 @@ class AnnotationsTest(fixtures.TestBase): annot_2 = s1._annotate({}) assert isinstance(annot_2.c.foo, Column) + def test_custom_construction_correct_anno_subclass(self): + # [ticket:2918] + from sqlalchemy.schema import Column + from sqlalchemy.sql.elements import AnnotatedColumnElement + class MyColumn(Column): + pass + + assert isinstance( + MyColumn('x', Integer)._annotate({"foo": "bar"}), + AnnotatedColumnElement) + + def test_custom_construction_correct_anno_expr(self): + # [ticket:2918] + from sqlalchemy.schema import Column + class MyColumn(Column): + pass + + col = MyColumn('x', Integer) + binary_1 = col == 5 + col_anno = MyColumn('x', Integer)._annotate({"foo": "bar"}) + binary_2 = col_anno == 5 + eq_(binary_2.left._annotations, {"foo": "bar"}) + def test_annotated_corresponding_column(self): table1 = table('table1', column("col1"))