]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- [feature] query.filter() accepts multiple
authorMike Bayer <mike_mp@zzzcomputing.com>
Sat, 7 Jan 2012 20:15:27 +0000 (15:15 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sat, 7 Jan 2012 20:15:27 +0000 (15:15 -0500)
criteria which will join via AND, i.e.
query.filter(x==y, z>q, ...)

CHANGES
lib/sqlalchemy/orm/query.py
test/orm/test_query.py

diff --git a/CHANGES b/CHANGES
index 178cac3b3613c2c28fb432ebd32111d51665b3b7..e2d6e827cb39f6a178748bfe96ed5d2fefc5ca1b 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -19,6 +19,10 @@ CHANGES
     declarative_base().  Allows two or more declarative
     bases to share the same registry of class names.
 
+  - [feature] query.filter() accepts multiple 
+    criteria which will join via AND, i.e.
+    query.filter(x==y, z>q, ...)
+
 - Py3K
   - [bug] Fixed inappropriate usage of util.py3k
     flag and renamed it to util.py3k_warning, since 
index f48189098d405b9d0215b5bea2baa719710ee60e..9508cb5321eb504990c8f738eb181b5605897dd4 100644 (file)
@@ -1066,33 +1066,67 @@ class Query(object):
         self._params.update(kwargs)
 
     @_generative(_no_statement_condition, _no_limit_offset)
-    def filter(self, criterion):
-        """apply the given filtering criterion to the query and return 
-        the newly resulting ``Query``
+    def filter(self, *criterion):
+        """apply the given filtering criterion to a copy
+        of this :class:`.Query`, using SQL expressions.
 
-        the criterion is any sql.ClauseElement applicable to the WHERE clause
-        of a select.
+        e.g.::
+        
+            session.query(MyClass).filter(MyClass.name == 'some name')
+        
+        Multiple criteria are joined together by AND (new in 0.7.5)::
+        
+            session.query(MyClass).\\
+                filter(MyClass.name == 'some name', MyClass.id > 5)
+        
+        The criterion is any SQL expression object applicable to the 
+        WHERE clause of a select.   String expressions are coerced
+        into SQL expression constructs via the :func:`.text` construct.
+
+        See also:
+        
+        :meth:`.Query.filter_by` - filter on keyword expressions.
 
         """
-        if isinstance(criterion, basestring):
-            criterion = sql.text(criterion)
+        for criterion in list(criterion):
+            if isinstance(criterion, basestring):
+                criterion = sql.text(criterion)
 
-        if criterion is not None and \
-                not isinstance(criterion, sql.ClauseElement):
-            raise sa_exc.ArgumentError(
-                        "filter() argument must be of type "
-                        "sqlalchemy.sql.ClauseElement or string")
+            if criterion is not None and \
+                    not isinstance(criterion, sql.ClauseElement):
+                raise sa_exc.ArgumentError(
+                            "filter() argument must be of type "
+                            "sqlalchemy.sql.ClauseElement or string")
 
-        criterion = self._adapt_clause(criterion, True, True)
+            criterion = self._adapt_clause(criterion, True, True)
 
-        if self._criterion is not None:
-            self._criterion = self._criterion & criterion
-        else:
-            self._criterion = criterion
+            if self._criterion is not None:
+                self._criterion = self._criterion & criterion
+            else:
+                self._criterion = criterion
 
     def filter_by(self, **kwargs):
-        """apply the given filtering criterion to the query and return 
-        the newly resulting ``Query``."""
+        """apply the given filtering criterion to a copy
+        of this :class:`.Query`, using keyword expressions.
+        
+        e.g.::
+        
+            session.query(MyClass).filter_by(name = 'some name')
+        
+        Multiple criteria are joined together by AND::
+        
+            session.query(MyClass).\\
+                filter_by(name = 'some name', id = 5)
+        
+        The keyword expressions are extracted from the primary 
+        entity of the query, or the last entity that was the 
+        target of a call to :meth:`.Query.join`.
+        
+        See also:
+        
+        :meth:`.Query.filter` - filter on SQL expressions.
+        
+        """
 
         clauses = [_entity_descriptor(self._joinpoint_zero(), key) == value
             for key, value in kwargs.iteritems()]
index 9ada53bfcd94ed9422c66352285c5eee543e519b..8a8ef0d4e561e010c513a788484f658265313d1d 100644 (file)
@@ -1139,6 +1139,7 @@ class FilterTest(QueryTest, AssertsCompiledSQL):
         eq_(sess.query(Item).filter(Item.keywords==None).order_by(Item.id).all(), [Item(id=4), Item(id=5)])
         eq_(sess.query(Item).filter(Item.keywords!=None).order_by(Item.id).all(), [Item(id=1),Item(id=2), Item(id=3)])
 
+
     def test_filter_by(self):
         User, Address = self.classes.User, self.classes.Address
 
@@ -1199,7 +1200,31 @@ class FilterTest(QueryTest, AssertsCompiledSQL):
             create_session().query(User.id).filter_by(**{}).order_by(User.id).all()
         )
 
+    def test_filter_conjunctions(self):
+        User = self.classes.User
+        s = create_session()
+        self.assert_compile(
+            s.query(User).filter(User.name=="ed", User.id>5),
+            "SELECT users.id AS users_id, users.name "
+            "AS users_name FROM users WHERE users.name = "
+            ":name_1 AND users.id > :id_1"
+        )
+
+        self.assert_compile(
+            s.query(User).filter_by(name='ed', id=5),
+            "SELECT users.id AS users_id, users.name "
+            "AS users_name FROM users WHERE users.name "
+            "= :name_1 AND users.id = :id_1"
+        )
 
+    def test_text_coerce(self):
+        User = self.classes.User
+        s = create_session()
+        self.assert_compile(
+            s.query(User).filter("name='ed'"),
+            "SELECT users.id AS users_id, users.name "
+            "AS users_name FROM users WHERE name='ed'"
+        )
 
 class SetOpsTest(QueryTest, AssertsCompiledSQL):
     __dialect__ = 'default'