]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Fixed bug where the "single table inheritance" criteria would be
authorMike Bayer <mike_mp@zzzcomputing.com>
Wed, 11 Nov 2015 17:57:32 +0000 (12:57 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 11 Nov 2015 17:57:32 +0000 (12:57 -0500)
added onto the end of a query in some inappropriate situations, such
as when querying from an exists() of a single-inheritance subclass.

fixes #3582

doc/build/changelog/changelog_11.rst
doc/build/changelog/migration_11.rst
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/sql/util.py
test/orm/inheritance/test_single.py

index 688818a2a3b5d1f34e77b264ab9687cb642b333c..9ce3975c2fb3542ba2297368d1034f318a334002 100644 (file)
 .. changelog::
     :version: 1.1.0b1
 
+    .. change::
+        :tags: bug, orm
+        :tickets: 3582
+
+        Fixed bug where the "single table inheritance" criteria would be
+        added onto the end of a query in some inappropriate situations, such
+        as when querying from an exists() of a single-inheritance subclass.
+
+        .. seealso::
+
+            :ref:`change_3582`
+
     .. change::
         :tags: enhancement, schema
         :pullreq: github:204
index 5b7c8321a0f733b9e1b7ecbd1656a223d0a18745..f43cfa87c0ef947b314bdad4cf7b53b340a32d0b 100644 (file)
@@ -16,7 +16,7 @@ What's New in SQLAlchemy 1.1?
     some issues may be moved to later milestones in order to allow
     for a timely release.
 
-    Document last updated: October 7, 2015
+    Document last updated: November 11, 2015
 
 Introduction
 ============
@@ -253,6 +253,44 @@ configuration of the existing object-level technique of assigning
 
 :ticket:`3250`
 
+
+.. _change_3582:
+
+Further Fixes to single-table inheritance querying
+--------------------------------------------------
+
+Continuing from 1.0's :ref:`migration_3177`, the :class:`.Query` should
+no longer inappropriately add the "single inheritance" criteria when the
+query is against a subquery expression such as an exists::
+
+    class Widget(Base):
+        __tablename__ = 'widget'
+        id = Column(Integer, primary_key=True)
+        type = Column(String)
+        data = Column(String)
+        __mapper_args__ = {'polymorphic_on': type}
+
+
+    class FooWidget(Widget):
+        __mapper_args__ = {'polymorphic_identity': 'foo'}
+
+    q = session.query(FooWidget).filter(FooWidget.data == 'bar').exists()
+
+    session.query(q).all()
+
+Produces::
+
+    SELECT EXISTS (SELECT 1
+    FROM widget
+    WHERE widget.data = :data_1 AND widget.type IN (:type_1)) AS anon_1
+
+The IN clause on the inside is appropriate, in order to limit to FooWidget
+objects, however previously the IN clause would also be generated a second
+time on the outside of the subquery.
+
+:ticket:`3582`
+
+
 New Features and Improvements - Core
 ====================================
 
index 3b51b80ba08024cf0c298360c343328b2b0a7d5b..84fb04d80789d74204c44eba1d5df03a5c26b812 100644 (file)
@@ -3676,7 +3676,7 @@ class _ColumnEntity(_QueryEntity):
             self._from_entities = set(self.entities)
         else:
             all_elements = [
-                elem for elem in visitors.iterate(column, {})
+                elem for elem in sql_util.surface_column_elements(column)
                 if 'parententity' in elem._annotations
             ]
 
index cbd74faacc9bd6191ceb67a5142978fc63b2cd89..5def7044428a1330e8414a7f096a64cd7efd59ab 100644 (file)
@@ -203,6 +203,21 @@ def surface_selectables(clause):
             stack.append(elem.element)
 
 
+def surface_column_elements(clause):
+    """traverse and yield only outer-exposed column elements, such as would
+    be addressable in the WHERE clause of a SELECT if this element were
+    in the columns clause."""
+
+    stack = deque([clause])
+    while stack:
+        elem = stack.popleft()
+        yield elem
+        for sub in elem.get_children():
+            if isinstance(elem, FromGrouping):
+                continue
+            stack.append(sub)
+
+
 def selectables_overlap(left, right):
     """Return True if left/right have some overlapping selectable"""
 
index 9f5d21a430c0ffe28e5d83a5f32721a6af7a2e45..0d102c06562a0dad331a5335e056f4d6a3b083f1 100644 (file)
@@ -9,6 +9,8 @@ from sqlalchemy.testing.schema import Table, Column
 
 
 class SingleInheritanceTest(testing.AssertsCompiledSQL, fixtures.MappedTest):
+    __dialect__ = 'default'
+
     @classmethod
     def define_tables(cls, metadata):
         Table('employees', metadata,
@@ -208,6 +210,19 @@ class SingleInheritanceTest(testing.AssertsCompiledSQL, fixtures.MappedTest):
         eq_(sess.query(Manager).filter(Manager.name.like('%m%')).count(), 2)
         eq_(sess.query(Employee).filter(Employee.name.like('%m%')).count(), 3)
 
+    def test_exists_standalone(self):
+        Engineer = self.classes.Engineer
+
+        sess = create_session()
+
+        self.assert_compile(
+            sess.query(
+                sess.query(Engineer).filter(Engineer.name == 'foo').exists()),
+            "SELECT EXISTS (SELECT 1 FROM employees WHERE "
+            "employees.name = :name_1 AND employees.type "
+            "IN (:type_1, :type_2)) AS anon_1"
+        )
+
     def test_type_filtering(self):
         Employee, Manager, reports, Engineer = (self.classes.Employee,
                                 self.classes.Manager,