]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- query.join() can now accept class-mapped attributes
authorMike Bayer <mike_mp@zzzcomputing.com>
Mon, 14 Jan 2008 04:20:26 +0000 (04:20 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Mon, 14 Jan 2008 04:20:26 +0000 (04:20 +0000)
as arguments, which can be used in place or in any
combination with strings.  In particular this allows
construction of joins to subclasses on a polymorphic
relation, i.e.
query(Company).join(['employees', Engineer.name]),
etc.

CHANGES
lib/sqlalchemy/orm/query.py
lib/sqlalchemy/sql/expression.py
test/orm/inheritance/query.py

diff --git a/CHANGES b/CHANGES
index 3f3f178aea0e521eda29022a4b0fe8de5af379a9..a940248185790b23e5dd6940e7075c0c4d80b3eb 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -18,7 +18,15 @@ CHANGES
       UPDATE operation are post-fetched immediately, instead
       of being deferred until later.  This mimics the old 0.3
       behavior.
-
+    
+    - query.join() can now accept class-mapped attributes
+      as arguments, which can be used in place or in any
+      combination with strings.  In particular this allows 
+      construction of joins to subclasses on a polymorphic 
+      relation, i.e. 
+      query(Company).join(['employees', Engineer.name]), 
+      etc.
+      
     - general improvements to the behavior of join() in 
       conjunction with polymorphic mappers, i.e. joining
       from/to polymorphic mappers and properly applying 
index b3678f1aa071f244e1e159bf76252f0c16555fdc..bec6f1c3eba59cb8bb369e00ffcac76e5bf29b2b 100644 (file)
@@ -24,6 +24,7 @@ from sqlalchemy.sql import expression, visitors, operators
 from sqlalchemy.orm import mapper, object_mapper
 from sqlalchemy.orm.mapper import _state_mapper
 from sqlalchemy.orm import util as mapperutil
+from sqlalchemy.orm import interfaces
 
 __all__ = ['Query', 'QueryContext']
 
@@ -422,7 +423,11 @@ class Query(object):
         mapper = start
         alias = self._aliases
         for key in util.to_list(keys):
-            prop = mapper.get_property(key, resolve_synonyms=True)
+            if isinstance(key, interfaces.PropComparator):
+                prop = key.property
+            else:
+                prop = mapper.get_property(key, resolve_synonyms=True)
+                
             if prop._is_self_referential() and not create_aliases:
                 raise exceptions.InvalidRequestError("Self-referential query on '%s' property requires create_aliases=True argument." % str(prop))
 
@@ -583,21 +588,39 @@ class Query(object):
         return q
 
     def join(self, prop, id=None, aliased=False, from_joinpoint=False):
-        """create a join of this ``Query`` object's criterion
-        to a relationship and return the newly resulting ``Query``.
-
-        'prop' may be a string property name or a list of string
-        property names.
+        """Create a join against this ``Query`` object's criterion
+        and apply generatively, retunring the newly resulting ``Query``.
+
+        'prop' may be one of:
+          * a string property name, i.e. "rooms"
+          * a class-mapped attribute, i.e. Houses.rooms
+          * a list containing a combination of any of the above.
+          
+        e.g.::
+        
+            session.query(Company).join('employees')
+            session.query(Company).join(['employees', 'tasks'])
+            session.query(Houses).join([Colonials.rooms, Room.closets])
+        
         """
 
         return self._join(prop, id=id, outerjoin=False, aliased=aliased, from_joinpoint=from_joinpoint)
 
     def outerjoin(self, prop, id=None, aliased=False, from_joinpoint=False):
-        """create a left outer join of this ``Query`` object's criterion
-        to a relationship and return the newly resulting ``Query``.
-
-        'prop' may be a string property name or a list of string
-        property names.
+        """Create a left outer join against this ``Query`` object's criterion
+        and apply generatively, retunring the newly resulting ``Query``.
+
+        'prop' may be one of:
+          * a string property name, i.e. "rooms"
+          * a class-mapped attribute, i.e. Houses.rooms
+          * a list containing a combination of any of the above.
+          
+        e.g.::
+        
+            session.query(Company).outerjoin('employees')
+            session.query(Company).outerjoin(['employees', 'tasks'])
+            session.query(Houses).outerjoin([Colonials.rooms, Room.closets])
+        
         """
 
         return self._join(prop, id=id, outerjoin=True, aliased=aliased, from_joinpoint=from_joinpoint)
index 187b38a2c11eceede7f7f3c4712467f43a3a5c14..4eb555d4d4fc5ae5df36c8999cac3754da3fa506 100644 (file)
@@ -26,7 +26,7 @@ to stay the same in future releases.
 """
 
 import datetime, re
-from itertools import chain
+import itertools
 from sqlalchemy import util, exceptions
 from sqlalchemy.sql import operators, visitors
 from sqlalchemy import types as sqltypes
@@ -2349,7 +2349,7 @@ class Join(FromClause):
         return self.select(use_labels=True, correlate=False).alias(name)
 
     def _hide_froms(self):
-        return chain(*[x.left._get_from_objects() + x.right._get_from_objects() for x in self._cloned_set])
+        return itertools.chain(*[x.left._get_from_objects() + x.right._get_from_objects() for x in self._cloned_set])
     _hide_froms = property(_hide_froms)
 
     def _get_from_objects(self, **modifiers):
index 2a15ae1b0194a3f7af8ee19793aa6f9e9aa0077a..5537f94a5d5722ce45a8b358478f313cb4bfbcca 100644 (file)
@@ -22,6 +22,9 @@ class Manager(Person):
 class Boss(Manager):
     pass
 
+class Machine(fixtures.Base):
+    pass
+    
 class Paperwork(fixtures.Base):
     pass
 
@@ -30,7 +33,7 @@ class PolymorphicQueryTest(ORMTest):
     keep_mappers = True
 
     def define_tables(self, metadata):
-        global companies, people, engineers, managers, boss, paperwork
+        global companies, people, engineers, managers, boss, paperwork, machines
 
         companies = Table('companies', metadata,
            Column('company_id', Integer, Sequence('company_id_seq', optional=True), primary_key=True),
@@ -48,7 +51,12 @@ class PolymorphicQueryTest(ORMTest):
            Column('engineer_name', String(50)),
            Column('primary_language', String(50)),
           )
-
+         
+        machines = Table('machines', metadata,
+            Column('machine_id', Integer, primary_key=True),
+            Column('name', String(50)),
+            Column('engineer_id', Integer, ForeignKey('engineers.person_id')))
+            
         managers = Table('managers', metadata,
            Column('person_id', Integer, ForeignKey('people.person_id'), primary_key=True),
            Column('status', String(30)),
@@ -82,12 +90,16 @@ class PolymorphicQueryTest(ORMTest):
             'employees':relation(Person)
         })
         
+        mapper(Machine, machines)
+        
         # testing a order_by here as well; the surrogate mapper has to adapt it
         mapper(Person, people, select_table=person_join, polymorphic_on=people.c.type, polymorphic_identity='person', order_by=people.c.person_id, 
             properties={
                 'paperwork':relation(Paperwork)
             })
-        mapper(Engineer, engineers, inherits=Person, polymorphic_identity='engineer')
+        mapper(Engineer, engineers, inherits=Person, polymorphic_identity='engineer', properties={
+                'machines':relation(Machine)
+            })
         mapper(Manager, managers, select_table=manager_join, inherits=Person, polymorphic_identity='manager')
         mapper(Boss, boss, inherits=Manager, polymorphic_identity='boss')
         mapper(Paperwork, paperwork)
@@ -100,10 +112,15 @@ class PolymorphicQueryTest(ORMTest):
         e1 = Engineer(name="dilbert", engineer_name="dilbert", primary_language="java", status="regular engineer", paperwork=[
             Paperwork(description="tps report #1"),
             Paperwork(description="tps report #2")
+        ], machines=[
+            Machine(name='IBM ThinkPad'),
+            Machine(name='IPhone'),
         ])
         e2 = Engineer(name="wally", engineer_name="wally", primary_language="c++", status="regular engineer", paperwork=[
             Paperwork(description="tps report #3"),
             Paperwork(description="tps report #4")
+        ], machines=[
+            Machine(name="Commodore 64")
         ])
         b1 = Boss(name="pointy haired boss", golf_swing="fore", manager_name="pointy", status="da boss", paperwork=[
             Paperwork(description="review #1"),
@@ -116,7 +133,11 @@ class PolymorphicQueryTest(ORMTest):
 
         e3 = Engineer(name="vlad", engineer_name="vlad", primary_language="cobol", status="elbonian engineer", paperwork=[
             Paperwork(description='elbonian missive #3')
+        ], machines=[
+                Machine(name="Commodore 64"),
+                Machine(name="IBM 3270")
         ])
+        
         c2.employees = [e3]
         sess = create_session()
         sess.save(c1)
@@ -160,6 +181,17 @@ class PolymorphicQueryTest(ORMTest):
 
         self.assertEquals(sess.query(Company).join('employees', aliased=True).filter(Person.name=='vlad').one(), c2)
     
+    def test_join_to_subclass(self):
+        sess = create_session()
+
+        self.assertEquals(sess.query(Person).join(Engineer.machines).all(), [e1, e2, e3])
+
+        self.assertEquals(sess.query(Person).join(Engineer.machines).filter(Machine.name.ilike("%ibm%")).all(), [e1, e3])
+        
+        self.assertEquals(sess.query(Company).join(['employees', Engineer.machines]).all(), [c1, c2])
+
+        self.assertEquals(sess.query(Company).join(['employees', Engineer.machines]).filter(Machine.name.ilike("%thinkpad%")).all(), [c1])
+        
     def test_join_through_polymorphic(self):
         sess = create_session()