diff options
-rw-r--r-- | CHANGES | 62 | ||||
-rw-r--r-- | lib/sqlalchemy/orm/query.py | 13 | ||||
-rw-r--r-- | test/orm/query.py | 16 |
3 files changed, 86 insertions, 5 deletions
@@ -13,18 +13,72 @@ CHANGES - the functionality of query.with_polymorphic() has been added to mapper() as a configuration option. + It's set via several forms: with_polymorphic='*' - with_polymorphic=(mappers, selectable) with_polymorphic=[mappers] + with_polymorphic=('*', selectable) + with_polymorphic=([mappers], selectable) + + This controls the default polymorphic loading strategy + for inherited mappers. When a selectable is not given, + outer joins are created for all joined-table inheriting + mappers requested. Note that the auto-create of joins + is not compatible with concrete table inheritance. - The existing select_table flag to mapper() is now + The existing select_table flag on mapper() is now deprecated and is synonymous with with_polymorphic('*', select_table). Note that the underlying "guts" of select_table have been completely removed and replaced with the newer, - more flexible approach. - + more flexible approach. + + The new approach also automatically allows eager loads + to work for subclasses, if they are present, for + example + sess.query(Company).options( + eagerload_all( + [Company.employees.of_type(Engineer), 'machines'] + )) + to load Company objects, their employees, and the + 'machines' collection of employees who happen to be + Engineers. A "with_polymorphic" Query option should be + introduced soon as well which would allow per-Query + control of with_polymorphic() on relations. + + - added two "experimental" features to Query, + "experimental" in that their specific name/behavior + is not carved in stone just yet: _values() and + _from_self(). We'd like feedback on these. + + - _values(*columns) is given a list of column + expressions, and returns a new Query that only + returns those columns. When evaluated, the return + value is a list of tuples just like when using + add_column() or add_entity(), the only difference is + that "entity zero", i.e. the mapped class, is not + included in the results. This means it finally makes + sense to use group_by() and having() on Query, which + have been sitting around uselessly until now. + + A future change to this method may include that its + ability to join, filter and allow other options not + related to a "resultset" are removed, so the feedback + we're looking for is how people want to use + _values()...i.e. at the very end, or do people prefer + to continue generating after it's called. + + - _from_self() compiles the SELECT statement for the + Query (minus any eager loaders), and returns a new + Query that selects from that SELECT. So basically you + can query from a Query without needing to extract the + SELECT statement manually. This gives meaning to + operations like query[3:5]._from_self().filter(some + criterion). There's not much controversial here + except that you can quickly create highly nested + queries that are less efficient, and we want feedback + on the naming choice. + - query.order_by() and query.group_by() will accept multiple arguments using *args (like select() already does). diff --git a/lib/sqlalchemy/orm/query.py b/lib/sqlalchemy/orm/query.py index 1d4b24e0c..9751cc9ba 100644 --- a/lib/sqlalchemy/orm/query.py +++ b/lib/sqlalchemy/orm/query.py @@ -360,7 +360,18 @@ class Query(object): q._entities = q._entities + [_MapperEntity(mapper=entity, alias=alias, id=id)] return q - + + def _from_self(self): + """return a Query that selects from this Query's SELECT statement. + + The API for this method hasn't been decided yet and is subject to change. + """ + + q = self._clone() + q._eager_loaders = util.Set() + fromclause = q.compile() + return Query(self.mapper, self.session).select_from(fromclause) + def _values(self, *columns): """Turn this query into a 'columns only' query. diff --git a/test/orm/query.py b/test/orm/query.py index a4dbb938d..b985202b4 100644 --- a/test/orm/query.py +++ b/test/orm/query.py @@ -408,6 +408,22 @@ class FilterTest(QueryTest): # o2m self.assertEquals([User(id=10)], sess.query(User).filter(User.addresses==None).all()) self.assertEquals([User(id=7),User(id=8),User(id=9)], sess.query(User).filter(User.addresses!=None).all()) + +class FromSelfTest(QueryTest): + def test_filter(self): + + assert [User(id=8), User(id=9)] == create_session().query(User).filter(User.id.in_([8,9]))._from_self().all() + + assert [User(id=8), User(id=9)] == create_session().query(User)[1:3]._from_self().all() + assert [User(id=8)] == list(create_session().query(User).filter(User.id.in_([8,9]))._from_self()[0:1]) + + def test_join(self): + assert [ + (User(id=8), Address(id=2)), + (User(id=8), Address(id=3)), + (User(id=8), Address(id=4)), + (User(id=9), Address(id=5)) + ] == create_session().query(User).filter(User.id.in_([8,9]))._from_self().join('addresses').add_entity(Address).all() class AggregateTest(QueryTest): def test_sum(self): |