← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/stormify-yet-more-bug-search into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/stormify-yet-more-bug-search into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/stormify-yet-more-bug-search/+merge/101863

This branch Stormifies another couple of bits of bug search: bug supervisor and tag filtering.

The only significant query change is switching from "(foo, bar) IN (SELECT foo, bar FROM baz)" syntax to the more explicit "ROW(foo, bar) IN (SELECT foo, bar FROM baz)", to work around an arguable Storm limitation (it otherwise compiles it as "foo, bar IN (SELECT foo, bar FROM baz)"). But that change shouldn't affect anything at all.
-- 
https://code.launchpad.net/~wgrant/launchpad/stormify-yet-more-bug-search/+merge/101863
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/stormify-yet-more-bug-search into lp:launchpad.
=== modified file 'lib/lp/bugs/model/bugtasksearch.py'
--- lib/lp/bugs/model/bugtasksearch.py	2012-04-12 23:43:12 +0000
+++ lib/lp/bugs/model/bugtasksearch.py	2012-04-13 05:40:26 +0000
@@ -20,10 +20,12 @@
     Desc,
     Exists,
     In,
+    Intersect,
     Join,
     LeftJoin,
     Not,
     Or,
+    Row,
     Select,
     SQL,
     )
@@ -605,21 +607,28 @@
     # is not for subscription to notifications.
     # See bug #191809
     if params.bug_supervisor:
-        bug_supervisor_clause = """(
-            BugTask.product IN (
-                SELECT id FROM Product
-                WHERE Product.bug_supervisor = %(bug_supervisor)s)
-            OR
-            ((BugTask.distribution, Bugtask.sourcepackagename) IN
-                (SELECT distribution,  sourcepackagename FROM
-                    StructuralSubscription
-                    WHERE subscriber = %(bug_supervisor)s))
-            OR
-            BugTask.distribution IN (
-                SELECT id from Distribution WHERE
-                Distribution.bug_supervisor = %(bug_supervisor)s)
-            )""" % sqlvalues(bug_supervisor=params.bug_supervisor)
-        extra_clauses.append(bug_supervisor_clause)
+        extra_clauses.append(Or(
+            In(
+                BugTask.productID,
+                Select(
+                    Product.id, tables=[Product],
+                    where=Product.bug_supervisor == params.bug_supervisor)),
+            In(
+                BugTask.distributionID,
+                Select(
+                    Distribution.id, tables=[Distribution],
+                    where=(
+                        Distribution.bug_supervisor ==
+                            params.bug_supervisor))),
+            In(
+                Row(BugTask.distributionID, BugTask.sourcepackagenameID),
+                Select(
+                    ((StructuralSubscription.distributionID,
+                     StructuralSubscription.sourcepackagenameID),),
+                    tables=[StructuralSubscription],
+                    where=(
+                        StructuralSubscription.subscriber ==
+                            params.bug_supervisor)))))
 
     if params.bug_reporter:
         extra_clauses.append(Bug.owner == params.bug_reporter)
@@ -671,7 +680,7 @@
 
     clause, decorator = _get_bug_privacy_filter_with_decorator(params.user)
     if clause:
-        extra_clauses.append(clause)
+        extra_clauses.append(SQL(clause))
         decorators.append(decorator)
 
     hw_clause = _build_hardware_related_clause(params)
@@ -706,12 +715,7 @@
     if params.created_since:
         extra_clauses.append(BugTask.datecreated > params.created_since)
 
-    storm_clauses = []
-    for clause in extra_clauses:
-        if isinstance(clause, str):
-            clause = SQL(clause)
-        storm_clauses.append(clause)
-    query = And(storm_clauses)
+    query = And(extra_clauses)
 
     if not decorators:
         decorator = lambda x: x
@@ -1219,47 +1223,36 @@
 
 # Tag restrictions
 
-def _build_tag_set_query(joiner, tags):
-    """Return an SQL snippet to find whether a bug matches the given tags.
-
-    The tags are sorted so that testing the generated queries is
-    easier and more reliable.
-
-    This SQL is designed to be a sub-query where the parent SQL defines
-    Bug.id. It evaluates to TRUE or FALSE, indicating whether the bug
-    with Bug.id matches against the tags passed.
-
-    Returns None if no tags are passed.
-
-    :param joiner: The SQL set term used to join the individual tag
-        clauses, typically "INTERSECT" or "UNION".
-    :param tags: An iterable of valid tag names (not prefixed minus
-        signs, not wildcards).
+def _build_tag_set_query(clauses):
+    subselects = [
+        Select(1, tables=[BugTag], where=And(BugTag.bugID == Bug.id, clause))
+        for clause in clauses]
+    if len(subselects) == 1:
+        return Exists(subselects[0])
+    else:
+        return Exists(Intersect(*subselects))
+
+
+def _build_tag_set_query_all(tags):
+    """Return a Storm expression for bugs matching all given tags.
+
+    :param tags: An iterable of valid tags without - or + and not wildcards.
+    :return: A Storm expression or None if no tags were provided.
     """
-    tags = list(tags)
-    if tags == []:
+    if not tags:
         return None
-
-    joiner = " %s " % joiner
-    return "EXISTS (%s)" % joiner.join(
-        "SELECT TRUE FROM BugTag WHERE " +
-            "BugTag.bug = Bug.id AND BugTag.tag = %s" % quote(tag)
-        for tag in sorted(tags))
+    return _build_tag_set_query([BugTag.tag == tag for tag in sorted(tags)])
 
 
 def _build_tag_set_query_any(tags):
-    """Return a query fragment for bugs matching any tag.
+    """Return a Storm expression for bugs matching any given tag.
 
     :param tags: An iterable of valid tags without - or + and not wildcards.
-    :return: A string SQL query fragment or None if no tags were provided.
+    :return: A Storm expression or None if no tags were provided.
     """
-    tags = sorted(tags)
-    if tags == []:
+    if not tags:
         return None
-    return "EXISTS (%s)" % (
-        "SELECT TRUE FROM BugTag"
-        " WHERE BugTag.bug = Bug.id"
-        " AND BugTag.tag IN %s") % sqlvalues(tags)
+    return _build_tag_set_query([BugTag.tag.is_in(sorted(tags))])
 
 
 def _build_tag_search_clause(tags_spec):
@@ -1283,46 +1276,45 @@
     if find_all:
         # How to combine an include clause and an exclude clause when
         # both are generated.
-        combine_with = 'AND'
+        combine_with = And
         # The set of bugs that have *all* of the tags requested for
         # *inclusion*.
-        include_clause = _build_tag_set_query("INTERSECT", include)
+        include_clause = _build_tag_set_query_all(include)
         # The set of bugs that have *any* of the tags requested for
         # *exclusion*.
         exclude_clause = _build_tag_set_query_any(exclude)
     else:
         # How to combine an include clause and an exclude clause when
         # both are generated.
-        combine_with = 'OR'
+        combine_with = Or
         # The set of bugs that have *any* of the tags requested for
         # inclusion.
         include_clause = _build_tag_set_query_any(include)
         # The set of bugs that have *all* of the tags requested for
         # exclusion.
-        exclude_clause = _build_tag_set_query("INTERSECT", exclude)
+        exclude_clause = _build_tag_set_query_all(exclude)
 
+    universal_clause = (
+        Exists(Select(1, tables=[BugTag], where=BugTag.bugID == Bug.id)))
     # Search for the *presence* of any tag.
     if '*' in wildcards:
         # Only clobber the clause if not searching for all tags.
         if include_clause == None or not find_all:
-            include_clause = (
-                "EXISTS (SELECT TRUE FROM BugTag WHERE BugTag.bug = Bug.id)")
+            include_clause = universal_clause
 
     # Search for the *absence* of any tag.
     if '-*' in wildcards:
         # Only clobber the clause if searching for all tags.
         if exclude_clause == None or find_all:
-            exclude_clause = (
-                "EXISTS (SELECT TRUE FROM BugTag WHERE BugTag.bug = Bug.id)")
+            exclude_clause = universal_clause
 
     # Combine the include and exclude sets.
     if include_clause != None and exclude_clause != None:
-        return "(%s %s NOT %s)" % (
-            include_clause, combine_with, exclude_clause)
+        return combine_with(include_clause, Not(exclude_clause))
     elif include_clause != None:
-        return "%s" % include_clause
+        return include_clause
     elif exclude_clause != None:
-        return "NOT %s" % exclude_clause
+        return Not(exclude_clause)
     else:
         # This means that there were no tags (wildcard or specific) to
         # search for (which is allowed, even if it's a bit weird).

=== modified file 'lib/lp/bugs/model/tests/test_bugtask.py'
--- lib/lp/bugs/model/tests/test_bugtask.py	2012-04-12 09:09:24 +0000
+++ lib/lp/bugs/model/tests/test_bugtask.py	2012-04-13 05:40:26 +0000
@@ -287,7 +287,8 @@
 class TestBugTaskTagSearchClauses(TestCase):
 
     def searchClause(self, tag_spec):
-        return _build_tag_search_clause(tag_spec)
+        return convert_storm_clause_to_string(
+            _build_tag_search_clause(tag_spec))
 
     def assertEqualIgnoringWhitespace(self, expected, observed):
         return self.assertEqual(
@@ -295,16 +296,18 @@
             normalize_whitespace(observed))
 
     def test_empty(self):
-        # Specifying no tags is valid.
-        self.assertEqual(self.searchClause(any()), None)
-        self.assertEqual(self.searchClause(all()), None)
+        # Specifying no tags is valid. _build_tag_search_clause will
+        # return None, which compiles to 'NULL' here but will be ignored
+        # by bugtasksearch.
+        self.assertEqual(self.searchClause(any()), 'NULL')
+        self.assertEqual(self.searchClause(all()), 'NULL')
 
     def test_single_tag_presence_any(self):
         # The WHERE clause to test for the presence of a single
         # tag where at least one tag is desired.
         expected_query = (
             """EXISTS
-                 (SELECT TRUE FROM BugTag
+                 (SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id
                      AND BugTag.tag IN ('fred'))""")
         self.assertEqualIgnoringWhitespace(
@@ -316,7 +319,7 @@
         # tag where all tags are desired.
         expected_query = (
             """EXISTS
-                 (SELECT TRUE FROM BugTag
+                 (SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id
                      AND BugTag.tag = 'fred')""")
         self.assertEqualIgnoringWhitespace(
@@ -328,7 +331,7 @@
         # tag where at least one tag is desired.
         expected_query = (
             """NOT EXISTS
-                 (SELECT TRUE FROM BugTag
+                 (SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id
                      AND BugTag.tag = 'fred')""")
         self.assertEqualIgnoringWhitespace(
@@ -340,7 +343,7 @@
         # tag where all tags are desired.
         expected_query = (
             """NOT EXISTS
-                 (SELECT TRUE FROM BugTag
+                 (SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id
                      AND BugTag.tag IN ('fred'))""")
         self.assertEqualIgnoringWhitespace(
@@ -352,7 +355,7 @@
         # the same for an `any` query or an `all` query.
         expected_query = (
             """EXISTS
-                 (SELECT TRUE FROM BugTag
+                 (SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id)""")
         self.assertEqualIgnoringWhitespace(
             expected_query,
@@ -366,7 +369,7 @@
         # the same for an `any` query or an `all` query.
         expected_query = (
             """NOT EXISTS
-                 (SELECT TRUE FROM BugTag
+                 (SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id)""")
         self.assertEqualIgnoringWhitespace(
             expected_query,
@@ -380,7 +383,7 @@
         # several tags.
         self.assertEqualIgnoringWhitespace(
             """EXISTS
-                 (SELECT TRUE FROM BugTag
+                 (SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id
                      AND BugTag.tag IN ('bob', 'fred'))""",
             self.searchClause(any(u'fred', u'bob')))
@@ -389,7 +392,7 @@
         # a superset of "bugs with a specific tag".
         self.assertEqualIgnoringWhitespace(
             """EXISTS
-                 (SELECT TRUE FROM BugTag
+                 (SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id)""",
             self.searchClause(any(u'fred', u'*')))
 
@@ -398,20 +401,20 @@
         # tags.
         self.assertEqualIgnoringWhitespace(
             """NOT EXISTS
-                 (SELECT TRUE FROM BugTag
+                 ((SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id
-                     AND BugTag.tag = 'bob'
+                     AND BugTag.tag = 'bob')
                   INTERSECT
-                  SELECT TRUE FROM BugTag
+                  (SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id
-                     AND BugTag.tag = 'fred')""",
+                     AND BugTag.tag = 'fred'))""",
             self.searchClause(any(u'-fred', u'-bob')))
         # In an `any` query, a negative wildcard is superfluous in the
         # presence of other negative tags because "bugs without a
         # specific tag" is a superset of "bugs without any tags".
         self.assertEqualIgnoringWhitespace(
             """NOT EXISTS
-                 (SELECT TRUE FROM BugTag
+                 (SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id
                      AND BugTag.tag = 'fred')""",
             self.searchClause(any(u'-fred', u'-*')))
@@ -421,13 +424,13 @@
         # tags.
         self.assertEqualIgnoringWhitespace(
             """EXISTS
-                 (SELECT TRUE FROM BugTag
+                 ((SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id
-                     AND BugTag.tag = 'bob'
+                     AND BugTag.tag = 'bob')
                   INTERSECT
-                  SELECT TRUE FROM BugTag
+                  (SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id
-                     AND BugTag.tag = 'fred')""",
+                     AND BugTag.tag = 'fred'))""",
             self.searchClause(all(u'fred', u'bob')))
         # In an `all` query, a positive wildcard is superfluous in the
         # presence of other positive tags because "bugs with a
@@ -435,7 +438,7 @@
         # with one or more tags".
         self.assertEqualIgnoringWhitespace(
             """EXISTS
-                 (SELECT TRUE FROM BugTag
+                 (SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id
                      AND BugTag.tag = 'fred')""",
             self.searchClause(all(u'fred', u'*')))
@@ -445,7 +448,7 @@
         # tags.
         self.assertEqualIgnoringWhitespace(
             """NOT EXISTS
-                 (SELECT TRUE FROM BugTag
+                 (SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id
                      AND BugTag.tag IN ('bob', 'fred'))""",
             self.searchClause(all(u'-fred', u'-bob')))
@@ -455,7 +458,7 @@
         # tag".
         self.assertEqualIgnoringWhitespace(
             """NOT EXISTS
-                 (SELECT TRUE FROM BugTag
+                 (SELECT 1 FROM BugTag
                    WHERE BugTag.bug = Bug.id)""",
             self.searchClause(all(u'-fred', u'-*')))
 
@@ -464,77 +467,77 @@
         # specific tags or the absence of one or more other specific
         # tags.
         self.assertEqualIgnoringWhitespace(
-            """(EXISTS
-                  (SELECT TRUE FROM BugTag
+            """EXISTS
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
                       AND BugTag.tag IN ('fred'))
                 OR NOT EXISTS
-                  (SELECT TRUE FROM BugTag
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
-                      AND BugTag.tag = 'bob'))""",
+                      AND BugTag.tag = 'bob')""",
             self.searchClause(any(u'fred', u'-bob')))
         self.assertEqualIgnoringWhitespace(
-            """(EXISTS
-                  (SELECT TRUE FROM BugTag
+            """EXISTS
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
                       AND BugTag.tag IN ('eric', 'fred'))
                 OR NOT EXISTS
-                  (SELECT TRUE FROM BugTag
+                  ((SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
-                      AND BugTag.tag = 'bob'
+                      AND BugTag.tag = 'bob')
                    INTERSECT
-                   SELECT TRUE FROM BugTag
+                   (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
                       AND BugTag.tag = 'harry'))""",
             self.searchClause(any(u'fred', u'-bob', u'eric', u'-harry')))
         # The positive wildcard is dominant over other positive tags.
         self.assertEqualIgnoringWhitespace(
-            """(EXISTS
-                  (SELECT TRUE FROM BugTag
+            """EXISTS
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id)
                 OR NOT EXISTS
-                  (SELECT TRUE FROM BugTag
+                  ((SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
-                      AND BugTag.tag = 'bob'
+                      AND BugTag.tag = 'bob')
                    INTERSECT
-                   SELECT TRUE FROM BugTag
+                   (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
                       AND BugTag.tag = 'harry'))""",
             self.searchClause(any(u'fred', u'-bob', u'*', u'-harry')))
         # The negative wildcard is superfluous in the presence of
         # other negative tags.
         self.assertEqualIgnoringWhitespace(
-            """(EXISTS
-                  (SELECT TRUE FROM BugTag
+            """EXISTS
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
                       AND BugTag.tag IN ('eric', 'fred'))
                 OR NOT EXISTS
-                  (SELECT TRUE FROM BugTag
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
-                      AND BugTag.tag = 'bob'))""",
+                      AND BugTag.tag = 'bob')""",
             self.searchClause(any(u'fred', u'-bob', u'eric', u'-*')))
         # The negative wildcard is not superfluous in the absence of
         # other negative tags.
         self.assertEqualIgnoringWhitespace(
-            """(EXISTS
-                  (SELECT TRUE FROM BugTag
+            """EXISTS
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
                       AND BugTag.tag IN ('eric', 'fred'))
                 OR NOT EXISTS
-                  (SELECT TRUE FROM BugTag
-                    WHERE BugTag.bug = Bug.id))""",
+                  (SELECT 1 FROM BugTag
+                    WHERE BugTag.bug = Bug.id)""",
             self.searchClause(any(u'fred', u'-*', u'eric')))
         # The positive wildcard is dominant over other positive tags,
         # and the negative wildcard is superfluous in the presence of
         # other negative tags.
         self.assertEqualIgnoringWhitespace(
-            """(EXISTS
-                  (SELECT TRUE FROM BugTag
+            """EXISTS
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id)
                 OR NOT EXISTS
-                  (SELECT TRUE FROM BugTag
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
-                      AND BugTag.tag = 'harry'))""",
+                      AND BugTag.tag = 'harry')""",
             self.searchClause(any(u'fred', u'-*', u'*', u'-harry')))
 
     def test_mixed_tags_all(self):
@@ -542,99 +545,99 @@
         # specific tags and the absence of one or more other specific
         # tags.
         self.assertEqualIgnoringWhitespace(
-            """(EXISTS
-                  (SELECT TRUE FROM BugTag
+            """EXISTS
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
                       AND BugTag.tag = 'fred')
                 AND NOT EXISTS
-                  (SELECT TRUE FROM BugTag
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
-                      AND BugTag.tag IN ('bob')))""",
+                      AND BugTag.tag IN ('bob'))""",
             self.searchClause(all(u'fred', u'-bob')))
         self.assertEqualIgnoringWhitespace(
-            """(EXISTS
-                  (SELECT TRUE FROM BugTag
+            """EXISTS
+                  ((SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
-                      AND BugTag.tag = 'eric'
+                      AND BugTag.tag = 'eric')
                    INTERSECT
-                   SELECT TRUE FROM BugTag
+                   (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
-                      AND BugTag.tag = 'fred')
+                      AND BugTag.tag = 'fred'))
                 AND NOT EXISTS
-                  (SELECT TRUE FROM BugTag
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
-                      AND BugTag.tag IN ('bob', 'harry')))""",
+                      AND BugTag.tag IN ('bob', 'harry'))""",
             self.searchClause(all(u'fred', u'-bob', u'eric', u'-harry')))
         # The positive wildcard is superfluous in the presence of
         # other positive tags.
         self.assertEqualIgnoringWhitespace(
-            """(EXISTS
-                  (SELECT TRUE FROM BugTag
+            """EXISTS
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
                       AND BugTag.tag = 'fred')
                 AND NOT EXISTS
-                  (SELECT TRUE FROM BugTag
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
-                      AND BugTag.tag IN ('bob', 'harry')))""",
+                      AND BugTag.tag IN ('bob', 'harry'))""",
             self.searchClause(all(u'fred', u'-bob', u'*', u'-harry')))
         # The positive wildcard is not superfluous in the absence of
         # other positive tags.
         self.assertEqualIgnoringWhitespace(
-            """(EXISTS
-                  (SELECT TRUE FROM BugTag
+            """EXISTS
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id)
                 AND NOT EXISTS
-                  (SELECT TRUE FROM BugTag
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
-                      AND BugTag.tag IN ('bob', 'harry')))""",
+                      AND BugTag.tag IN ('bob', 'harry'))""",
             self.searchClause(all(u'-bob', u'*', u'-harry')))
         # The negative wildcard is dominant over other negative tags.
         self.assertEqualIgnoringWhitespace(
-            """(EXISTS
-                  (SELECT TRUE FROM BugTag
+            """EXISTS
+                  ((SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
-                      AND BugTag.tag = 'eric'
+                      AND BugTag.tag = 'eric')
                    INTERSECT
-                   SELECT TRUE FROM BugTag
+                   (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
-                      AND BugTag.tag = 'fred')
+                      AND BugTag.tag = 'fred'))
                 AND NOT EXISTS
-                  (SELECT TRUE FROM BugTag
-                    WHERE BugTag.bug = Bug.id))""",
+                  (SELECT 1 FROM BugTag
+                    WHERE BugTag.bug = Bug.id)""",
             self.searchClause(all(u'fred', u'-bob', u'eric', u'-*')))
         # The positive wildcard is superfluous in the presence of
         # other positive tags, and the negative wildcard is dominant
         # over other negative tags.
         self.assertEqualIgnoringWhitespace(
-            """(EXISTS
-                  (SELECT TRUE FROM BugTag
+            """EXISTS
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id
                       AND BugTag.tag = 'fred')
                 AND NOT EXISTS
-                  (SELECT TRUE FROM BugTag
-                    WHERE BugTag.bug = Bug.id))""",
+                  (SELECT 1 FROM BugTag
+                    WHERE BugTag.bug = Bug.id)""",
             self.searchClause(all(u'fred', u'-*', u'*', u'-harry')))
 
     def test_mixed_wildcards(self):
         # The WHERE clause to test for the presence of tags or the
         # absence of tags.
         self.assertEqualIgnoringWhitespace(
-            """(EXISTS
-                  (SELECT TRUE FROM BugTag
+            """EXISTS
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id)
                 OR NOT EXISTS
-                  (SELECT TRUE FROM BugTag
-                    WHERE BugTag.bug = Bug.id))""",
+                  (SELECT 1 FROM BugTag
+                    WHERE BugTag.bug = Bug.id)""",
             self.searchClause(any(u'*', u'-*')))
         # The WHERE clause to test for the presence of tags and the
         # absence of tags.
         self.assertEqualIgnoringWhitespace(
-            """(EXISTS
-                  (SELECT TRUE FROM BugTag
+            """EXISTS
+                  (SELECT 1 FROM BugTag
                     WHERE BugTag.bug = Bug.id)
                 AND NOT EXISTS
-                  (SELECT TRUE FROM BugTag
-                    WHERE BugTag.bug = Bug.id))""",
+                  (SELECT 1 FROM BugTag
+                    WHERE BugTag.bug = Bug.id)""",
             self.searchClause(all(u'*', u'-*')))
 
 

=== modified file 'lib/lp/services/database/stormexpr.py'
--- lib/lp/services/database/stormexpr.py	2012-04-12 04:09:11 +0000
+++ lib/lp/services/database/stormexpr.py	2012-04-13 05:40:26 +0000
@@ -8,6 +8,7 @@
     'CountDistinct',
     'Greatest',
     'NullCount',
+    'Row',
     ]
 
 from storm.expr import (
@@ -71,3 +72,8 @@
     args = compile(array.args, state)
     state.pop()
     return "ARRAY[%s]" % args
+
+
+class Row(NamedFunc):
+    __slots__ = ()
+    name = "ROW"