launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #10351
[Merge] lp:~adeuring/launchpad/bug-1020443-model into lp:launchpad
Abel Deuring has proposed merging lp:~adeuring/launchpad/bug-1020443-model into lp:launchpad.
Requested reviews:
Abel Deuring (adeuring): code
Related bugs:
Bug #1020443 in Launchpad itself: "ProgrammingError: spiexceptions.SyntaxError: syntax error in tsquery"
https://bugs.launchpad.net/launchpad/+bug/1020443
For more details, see:
https://code.launchpad.net/~adeuring/launchpad/bug-1020443-model/+merge/117076
Don't use the characters "&|!" as operators for full text search queries; avoid usgae of ftq(nl), where nl is the result of a call of nl_phrase_search(). This fixes bad search result ranking and avoids errors when the branch lp:~adeuring/launchpad/bug-1020443-db-patch will be merged.
See https://code.launchpad.net/~adeuring/launchpad/bug-1020443/+merge/115721 and https://code.launchpad.net/~adeuring/launchpad/bug-1020443-2/+merge/116876 for more details.
--
https://code.launchpad.net/~adeuring/launchpad/bug-1020443-model/+merge/117076
Your team Launchpad code reviewers is subscribed to branch lp:launchpad.
=== modified file 'lib/lp/answers/doc/faq-vocabulary.txt'
--- lib/lp/answers/doc/faq-vocabulary.txt 2012-07-27 00:25:42 +0000
+++ lib/lp/answers/doc/faq-vocabulary.txt 2012-07-27 14:59:41 +0000
@@ -82,7 +82,5 @@
2
>>> for term in terms:
... print term.title
+ How do I install Extensions?
How do I troubleshoot problems with extensions/themes?
- How do I install Extensions?
-
-
=== modified file 'lib/lp/answers/doc/faq.txt'
--- lib/lp/answers/doc/faq.txt 2012-07-27 00:25:42 +0000
+++ lib/lp/answers/doc/faq.txt 2012-07-27 14:59:41 +0000
@@ -232,7 +232,7 @@
>>> from lp.registry.interfaces.person import IPersonSet
>>> foo_bar = getUtility(IPersonSet).getByEmail('foo.bar@xxxxxxxxxxxxx')
>>> for faq in faqset.searchFAQs(
- ... search_text='java | flash', owner=foo_bar):
+ ... search_text='java OR flash', owner=foo_bar):
... print '%s (%s)' % (faq.title, faq.target.displayname)
How do I install plugins (Shockwave, QuickTime, etc.)? (Mozilla Firefox)
How can I play MP3/Divx/DVDs/Quicktime/Realmedia files
=== modified file 'lib/lp/answers/model/faq.py'
--- lib/lp/answers/model/faq.py 2012-07-27 00:25:42 +0000
+++ lib/lp/answers/model/faq.py 2012-07-27 14:59:41 +0000
@@ -138,7 +138,7 @@
return FAQ.select(
'%s AND FAQ.fti @@ %s' % (target_constraint, quote(fti_search)),
orderBy=[
- SQLConstant("-rank(FAQ.fti, ftq(%s))" % quote(fti_search)),
+ SQLConstant("-rank(FAQ.fti, %s::tsquery)" % quote(fti_search)),
"-FAQ.date_created"])
@staticmethod
=== modified file 'lib/lp/answers/model/question.py'
--- lib/lp/answers/model/question.py 2012-07-27 00:25:42 +0000
+++ lib/lp/answers/model/question.py 2012-07-27 14:59:41 +0000
@@ -864,6 +864,7 @@
product=None, distribution=None, sourcepackagename=None,
project=None):
self.search_text = search_text
+ self.nl_phrase_used = False
if zope_isinstance(status, DBItem):
self.status = [status]
@@ -944,8 +945,12 @@
constraints = self.getTargetConstraints()
if self.search_text is not None:
- constraints.append(
- 'Question.fti @@ ftq(%s)' % quote(self.search_text))
+ if self.nl_phrase_used:
+ constraints.append(
+ 'Question.fti @@ %s' % quote(self.search_text))
+ else:
+ constraints.append(
+ 'Question.fti @@ ftq(%s)' % quote(self.search_text))
if self.status:
constraints.append('Question.status IN %s' % sqlvalues(
@@ -1009,10 +1014,16 @@
elif sort is QuestionSort.RELEVANCY:
if self.search_text:
# SQLConstant is a workaround for bug 53455
- return [SQLConstant(
- "-rank(Question.fti, ftq(%s))" % quote(
- self.search_text)),
- "-Question.datecreated"]
+ if self.nl_phrase_used:
+ return [SQLConstant(
+ "-rank(Question.fti, %s::tsquery)" % quote(
+ self.search_text)),
+ "-Question.datecreated"]
+ else:
+ return [SQLConstant(
+ "-rank(Question.fti, ftq(%s))" % quote(
+ self.search_text)),
+ "-Question.datecreated"]
else:
return "-Question.datecreated"
elif sort is QuestionSort.RECENT_OWNER_ACTIVITY:
@@ -1113,6 +1124,7 @@
# similarity search algorithm.
self.search_text = nl_phrase_search(
title, Question, " AND ".join(self.getTargetConstraints()))
+ self.nl_phrase_used = True
class QuestionPersonSearch(QuestionSearch):
=== modified file 'lib/lp/answers/stories/this-is-a-faq.txt'
--- lib/lp/answers/stories/this-is-a-faq.txt 2012-07-27 00:25:42 +0000
+++ lib/lp/answers/stories/this-is-a-faq.txt 2012-07-27 14:59:41 +0000
@@ -66,8 +66,8 @@
... print radio, label, link
>>> printFAQOptions(user_browser.contents)
(*) No existing FAQs are relevant
+ ( ) 8: How do I install Extensions?
( ) 9: How do I troubleshoot problems with extensions/themes?
- ( ) 8: How do I install Extensions?
>>> print user_browser.getLink('How do I troubleshoot problems').url
http://answers.launchpad.dev/firefox/+faq/9
@@ -157,8 +157,8 @@
>>> printFAQOptions(user_browser.contents)
( ) No existing FAQs are relevant
(*) 10: How do I install plugins (Shockwave, QuickTime, etc.)?
+ ( ) 8: How do I install Extensions?
( ) 9: How do I troubleshoot problems with extensions/themes?
- ( ) 8: How do I install Extensions?
He changes the message and click 'Link to FAQ'.
=== modified file 'lib/lp/bugs/doc/bugtask-find-similar.txt'
--- lib/lp/bugs/doc/bugtask-find-similar.txt 2012-07-27 00:25:42 +0000
+++ lib/lp/bugs/doc/bugtask-find-similar.txt 2012-07-27 14:59:41 +0000
@@ -142,6 +142,7 @@
... distribution=test_distro)
>>> for bugtask in similar_bugs:
... print bugtask.bug.title
+ Nothing to do with cheese or sandwiches
This cheese sandwich should show up
>>> similar_bugs = getUtility(IBugTaskSet).findSimilar(
=== modified file 'lib/lp/bugs/doc/bugtask-search.txt'
--- lib/lp/bugs/doc/bugtask-search.txt 2012-07-27 00:25:42 +0000
+++ lib/lp/bugs/doc/bugtask-search.txt 2012-07-27 14:59:41 +0000
@@ -138,7 +138,7 @@
For example, there are no bugs with the word 'Fnord' in Firefox.
- >>> text_search = BugTaskSearchParams(user=None, fast_searchtext=u'Fnord')
+ >>> text_search = BugTaskSearchParams(user=None, searchtext=u'Fnord')
>>> found_bugtasks = firefox.searchTasks(text_search)
>>> found_bugtasks.count()
0
@@ -156,6 +156,63 @@
... print "#%s" % bugtask.bug.id
#4
+=== BugTaskSearchParams' parameters searchtext and fast_searchtext ===
+
+Normally, the parameter searchtext should be used. The alternative
+parameter fast_searchtext requires a syntactically correct tsquery
+expression containing stemmed words.
+
+A simple phrase can be passed as searchtext, but not as fast_searchtext,
+see below.
+
+ >>> good_search = BugTaskSearchParams(
+ ... user=None, searchtext=u'happens pretty often')
+ >>> found_bugtasks = firefox.searchTasks(good_search)
+ >>> for bugtask in found_bugtasks:
+ ... print "#%s" % bugtask.bug.id
+ #4
+
+The unstemmed word "happens" does not yield any results when used
+as fast_textsearch.
+
+ >>> bad_search = BugTaskSearchParams(
+ ... user=None, fast_searchtext=u'happens')
+ >>> found_bugtasks = firefox.searchTasks(bad_search)
+ >>> print found_bugtasks.count()
+ 0
+
+If the stem of "happens" is used, we get results.
+
+ >>> good_search = BugTaskSearchParams(
+ ... user=None, fast_searchtext=u'happen')
+ >>> found_bugtasks = firefox.searchTasks(good_search)
+ >>> for bugtask in found_bugtasks:
+ ... print "#%s" % bugtask.bug.id
+ #4
+ #6
+
+Stemmed words may be combined into a valid tsquery expression.
+
+ >>> good_search = BugTaskSearchParams(
+ ... user=None, fast_searchtext=u'happen&pretti&often')
+ >>> found_bugtasks = firefox.searchTasks(good_search)
+ >>> for bugtask in found_bugtasks:
+ ... print "#%s" % bugtask.bug.id
+ #4
+
+Passing invalid tsquery expressions as fast_searchtext raises an exception.
+
+ >>> bad_search = BugTaskSearchParams(
+ ... user=None, fast_searchtext=u'happens pretty often')
+ >>> list(firefox.searchTasks(bad_search))
+ Traceback (most recent call last):
+ ...
+ ProgrammingError: syntax error in tsquery: "happens pretty often"
+ ...
+
+ >>> import transaction
+ >>> transaction.abort()
+
== Searching by bug reporter ==
@@ -340,7 +397,6 @@
bugs are found for him:
>>> from lp.registry.interfaces.person import IPersonSet
- >>> import transaction
>>> transaction.abort()
>>> no_priv = getUtility(IPersonSet).getByName('no-priv')
=== modified file 'lib/lp/bugs/model/bugtasksearch.py'
--- lib/lp/bugs/model/bugtasksearch.py 2012-07-27 00:25:42 +0000
+++ lib/lp/bugs/model/bugtasksearch.py 2012-07-27 14:59:41 +0000
@@ -862,18 +862,21 @@
assert params.searchtext is None, (
'Cannot use searchtext at the same time as fast_searchtext.')
searchtext = params.fast_searchtext
+ fti_expression = "?::tsquery"
else:
assert params.fast_searchtext is None, (
'Cannot use fast_searchtext at the same time as searchtext.')
searchtext = params.searchtext
+ fti_expression = "ftq(?)"
if params.orderby is None:
# Unordered search results aren't useful, so sort by relevance
# instead.
params.orderby = [
- SQL("-rank(BugTaskFlat.fti, ftq(?))", params=(searchtext,))]
+ SQL("-rank(BugTaskFlat.fti, %s)" % fti_expression,
+ params=(searchtext,))]
- return SQL("BugTaskFlat.fti @@ ftq(?)", params=(searchtext,))
+ return SQL("BugTaskFlat.fti @@ %s" % fti_expression, params=(searchtext,))
def _build_status_clause(col, status):
=== modified file 'lib/lp/bugs/model/tests/test_bugtasksearch.py'
--- lib/lp/bugs/model/tests/test_bugtasksearch.py 2012-07-27 00:25:42 +0000
+++ lib/lp/bugs/model/tests/test_bugtasksearch.py 2012-07-27 14:59:41 +0000
@@ -285,9 +285,11 @@
def test_fast_fulltext_search(self):
# Fast full text searches find text indexed by Bug.fti...
+ # Note that a valid tsquery expression with stemmed words must
+ # be specified.
self.setUpFullTextSearchTests()
params = self.getBugTaskSearchParams(
- user=None, fast_searchtext=u'one title')
+ user=None, fast_searchtext=u'one&titl')
self.assertSearchFinds(params, self.bugtasks[:1])
def test_tags(self):
@@ -750,7 +752,7 @@
# Someone without permission to see deactiveated projects does
# not see bugtasks for deactivated projects.
bugtask_set = getUtility(IBugTaskSet)
- param = BugTaskSearchParams(user=None, fast_searchtext=u'Monkeys')
+ param = BugTaskSearchParams(user=None, searchtext=u'Monkeys')
results = bugtask_set.search(param, _noprejoins=True)
self.assertEqual([self.active_bugtask], list(results))
=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py 2012-07-27 00:25:42 +0000
+++ lib/lp/registry/browser/product.py 2012-07-27 14:59:41 +0000
@@ -205,7 +205,7 @@
)
-OR = '|'
+OR = ' OR '
SPACE = ' '
=== modified file 'lib/lp/registry/doc/vocabularies.txt'
--- lib/lp/registry/doc/vocabularies.txt 2012-07-27 00:25:42 +0000
+++ lib/lp/registry/doc/vocabularies.txt 2012-07-27 14:59:41 +0000
@@ -684,6 +684,11 @@
>>> checked_count == len(INACTIVE_ACCOUNT_STATUSES)
True
+It is possible to search for alternative names.
+
+ >>> [person.name for person in vocab.search('matsubara OR salgado')]
+ [u'matsubara', u'salgado']
+
AdminMergeablePerson
--------------------
@@ -738,6 +743,11 @@
>>> fooperson in vocab
False
+It is possible to search for alternative names.
+
+ >>> [(p.name) for p in vocab.search('matsubara OR salgado')]
+ [u'matsubara', u'salgado']
+
ValidPersonOrTeam
.................
@@ -1050,7 +1060,7 @@
[(u'testing Spanish team', u'Carlos Perell\xf3 Mar\xedn')]
>>> sorted((team.displayname, team.teamowner.displayname)
- ... for team in vocab.search('spanish | ubuntu'))
+ ... for team in vocab.search('spanish OR ubuntu'))
[(u'Mirror Administrators', u'Mark Shuttleworth'),
(u'Ubuntu Gnome Team', u'Mark Shuttleworth'),
(u'Ubuntu Security Team', u'Colin Watson'),
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2012-07-27 00:25:42 +0000
+++ lib/lp/registry/model/person.py 2012-07-27 14:59:41 +0000
@@ -3483,7 +3483,8 @@
return EmptyResultSet()
orderBy = Person._sortingColumnsForSetOperations
- text = ensure_unicode(text).lower()
+ text = ensure_unicode(text)
+ lower_case_text = text.lower()
# Teams may not have email addresses, so we need to either use a LEFT
# OUTER JOIN or do a UNION between four queries. Using a UNION makes
# it a lot faster than with a LEFT OUTER JOIN.
@@ -3493,7 +3494,7 @@
EmailAddress.person == Person.id,
Person.account == Account.id,
Not(Account.status.is_in(INACTIVE_ACCOUNT_STATUSES)),
- EmailAddress.email.lower().startswith(text))
+ EmailAddress.email.lower().startswith(lower_case_text))
store = IStore(Person)
@@ -3516,7 +3517,7 @@
results = results.union(store.find(
Person, person_name_query)).order_by()
- team_email_query = self._teamEmailQuery(text)
+ team_email_query = self._teamEmailQuery(lower_case_text)
results = results.union(
store.find(Person, team_email_query)).order_by()
team_name_query = self._teamNameQuery(text)
@@ -3530,7 +3531,7 @@
must_have_email=False, created_after=None, created_before=None):
"""See `IPersonSet`."""
orderBy = Person._sortingColumnsForSetOperations
- text = ensure_unicode(text).lower()
+ text = ensure_unicode(text)
store = IStore(Person)
base_query = And(
Person.teamowner == None,
@@ -3570,7 +3571,7 @@
email_query = And(
base_query,
EmailAddress.person == Person.id,
- EmailAddress.email.lower().startswith(ensure_unicode(text)))
+ EmailAddress.email.lower().startswith(text.lower()))
name_query = And(
base_query,
@@ -3583,11 +3584,11 @@
def findTeam(self, text=""):
"""See `IPersonSet`."""
orderBy = Person._sortingColumnsForSetOperations
- text = ensure_unicode(text).lower()
+ text = ensure_unicode(text)
# Teams may not have email addresses, so we need to either use a LEFT
# OUTER JOIN or do a UNION between two queries. Using a UNION makes
# it a lot faster than with a LEFT OUTER JOIN.
- email_query = self._teamEmailQuery(text)
+ email_query = self._teamEmailQuery(text.lower())
store = IStore(Person)
email_results = store.find(Person, email_query).order_by()
name_query = self._teamNameQuery(text)
=== modified file 'lib/lp/registry/tests/test_person_vocabularies.py'
--- lib/lp/registry/tests/test_person_vocabularies.py 2012-07-27 00:25:42 +0000
+++ lib/lp/registry/tests/test_person_vocabularies.py 2012-07-27 14:59:41 +0000
@@ -25,6 +25,7 @@
from lp.services.webapp.vocabulary import FilteredVocabularyBase
from lp.testing import (
login_person,
+ person_logged_in,
StormStatementRecorder,
TestCaseWithFactory,
)
@@ -188,6 +189,22 @@
name="fredteam", email="fredteam@xxxxxxx")
self._team_filter_tests([team])
+ def test_search_accepts_or_expressions(self):
+ person = self.factory.makePerson(name='baz')
+ team = self.factory.makeTeam(name='blah')
+ result = list(self.searchVocabulary(None, 'baz OR blah'))
+ self.assertEqual([person, team], result)
+ private_team_one = self.factory.makeTeam(
+ name='private-eye', visibility=PersonVisibility.PRIVATE,
+ owner=person)
+ private_team_two = self.factory.makeTeam(
+ name='paranoid', visibility=PersonVisibility.PRIVATE,
+ owner=person)
+ with person_logged_in(person):
+ result = list(
+ self.searchVocabulary(None, 'paranoid OR private-eye'))
+ self.assertEqual([private_team_one, private_team_two], result)
+
class TestValidPersonOrTeamPreloading(VocabularyTestBase,
TestCaseWithFactory):
@@ -373,13 +390,20 @@
def test_unvalidated_emails_ignored(self):
person = self.factory.makePerson()
- unvalidated_email = self.factory.makeEmail(
+ self.factory.makeEmail(
'fnord@xxxxxxxxxxx',
person,
email_status=EmailAddressStatus.NEW)
search = self.searchVocabulary(None, 'fnord@xxxxxxxxxxx')
self.assertEqual([], [s for s in search])
+ def test_search_accepts_or_expressions(self):
+ team_one = self.factory.makeTeam(name='baz')
+ team_two = self.factory.makeTeam(name='blah')
+ result = list(self.searchVocabulary(None, 'baz OR blah'))
+ self.assertEqual([team_one, team_two], result)
+
+
class TestNewPillarGranteeVocabulary(VocabularyTestBase,
TestCaseWithFactory):
"""Test that the NewPillarGranteeVocabulary behaves as expected."""
=== modified file 'lib/lp/registry/tests/test_personset.py'
--- lib/lp/registry/tests/test_personset.py 2012-07-27 00:25:42 +0000
+++ lib/lp/registry/tests/test_personset.py 2012-07-27 14:59:41 +0000
@@ -117,13 +117,13 @@
def test_getByEmail_ignores_unvalidated_emails(self):
person = self.factory.makePerson()
- unvalidated_email = self.factory.makeEmail(
+ self.factory.makeEmail(
'fnord@xxxxxxxxxxx',
person,
email_status=EmailAddressStatus.NEW)
found = self.person_set.getByEmail('fnord@xxxxxxxxxxx')
self.assertTrue(found is None)
-
+
def test_getPrecachedPersonsFromIDs(self):
# The getPrecachedPersonsFromIDs() method should only make one
# query to load all the extraneous data. Accessing the
@@ -183,6 +183,57 @@
self.person_set.getByOpenIDIdentifier(
u'http://not.launchpad.dev/+id/%s' % identifier))
+ def test_find__accepts_queries_with_or_operator(self):
+ # PersonSet.find() allows to search for OR combined terms.
+ person_one = self.factory.makePerson(name='baz')
+ person_two = self.factory.makeTeam(name='blah')
+ result = list(self.person_set.find('baz OR blah'))
+ self.assertEqual([person_one, person_two], result)
+
+ def test_findPerson__accepts_queries_with_or_operator(self):
+ # PersonSet.findPerson() allows to search for OR combined terms.
+ person_one = self.factory.makePerson(
+ name='baz', email='one@xxxxxxxxxxx')
+ person_two = self.factory.makePerson(
+ name='blah', email='two@xxxxxxxxxxx')
+ result = list(self.person_set.findPerson('baz OR blah'))
+ self.assertEqual([person_one, person_two], result)
+ # Note that these OR searches do not work for email addresses.
+ result = list(self.person_set.findPerson(
+ 'one@xxxxxxxxxxx OR two@xxxxxxxxxxx'))
+ self.assertEqual([], result)
+
+ def test_findPerson__case_insensitive_email_address_search(self):
+ # A search for email addresses is case insensitve.
+ person_one = self.factory.makePerson(
+ name='baz', email='ONE@xxxxxxxxxxx')
+ person_two = self.factory.makePerson(
+ name='blah', email='two@xxxxxxxxxxx')
+ result = list(self.person_set.findPerson('one@xxxxxxxxxxx'))
+ self.assertEqual([person_one], result)
+ result = list(self.person_set.findPerson('TWO@xxxxxxxxxxx'))
+ self.assertEqual([person_two], result)
+
+ def test_findTeam__accepts_queries_with_or_operator(self):
+ # PersonSet.findTeam() allows to search for OR combined terms.
+ team_one = self.factory.makeTeam(name='baz', email='ONE@xxxxxxxxxxx')
+ team_two = self.factory.makeTeam(name='blah', email='TWO@xxxxxxxxxxx')
+ result = list(self.person_set.findTeam('baz OR blah'))
+ self.assertEqual([team_one, team_two], result)
+ # Note that these OR searches do not work for email addresses.
+ result = list(self.person_set.findTeam(
+ 'one@xxxxxxxxxxx OR two@xxxxxxxxxxx'))
+ self.assertEqual([], result)
+
+ def test_findTeam__case_insensitive_email_address_search(self):
+ # A search for email addresses is case insensitve.
+ team_one = self.factory.makeTeam(name='baz', email='ONE@xxxxxxxxxxx')
+ team_two = self.factory.makeTeam(name='blah', email='TWO@xxxxxxxxxxx')
+ result = list(self.person_set.findTeam('one@xxxxxxxxxxx'))
+ self.assertEqual([team_one], result)
+ result = list(self.person_set.findTeam('TWO@xxxxxxxxxxx'))
+ self.assertEqual([team_two], result)
+
class TestPersonSetMergeMailingListSubscriptions(TestCaseWithFactory):
=== modified file 'lib/lp/registry/tests/test_product_vocabularies.py'
--- lib/lp/registry/tests/test_product_vocabularies.py 2012-07-27 00:25:42 +0000
+++ lib/lp/registry/tests/test_product_vocabularies.py 2012-07-27 14:59:41 +0000
@@ -66,6 +66,16 @@
self.assertEqual(
[quux_product, bar_product], list(result))
+ def test_search_with_or_expression(self):
+ # Searches for either of two or more names are possible.
+ blah_product = self.factory.makeProduct(
+ name='blah', displayname='Blah', summary='Blah blather')
+ baz_product = self.factory.makeProduct(
+ name='baz', displayname='Baz')
+ result = self.vocabulary.search('blah OR baz')
+ self.assertEqual(
+ [blah_product, baz_product], list(result))
+
def test_exact_match_is_first(self):
# When the flag is enabled, an exact name match always wins.
the_quux_product = self.factory.makeProduct(
=== added file 'lib/lp/registry/tests/test_projectgroup_vocabulary.py'
--- lib/lp/registry/tests/test_projectgroup_vocabulary.py 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/tests/test_projectgroup_vocabulary.py 2012-07-27 14:59:41 +0000
@@ -0,0 +1,26 @@
+# Copyright 2012 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test the ProjectGroup vocabulary."""
+
+__metaclass__ = type
+
+from lp.registry.vocabularies import ProjectGroupVocabulary
+from lp.testing import TestCaseWithFactory
+from lp.testing.layers import DatabaseFunctionalLayer
+
+
+class TestProjectGroupVocabulary(TestCaseWithFactory):
+ """Test that the ProjectGroupVocabulary behaves as expected."""
+ layer = DatabaseFunctionalLayer
+
+ def test_search_with_or_expression(self):
+ # Searches for either of two or more names are possible.
+ blah_group = self.factory.makeProject(
+ name='blah', displayname='Blah', summary='Blah blather')
+ baz_group = self.factory.makeProject(
+ name='baz', displayname='Baz')
+ vocabulary = ProjectGroupVocabulary()
+ result = vocabulary.search('blah OR baz')
+ self.assertEqual(
+ [blah_group, baz_group], list(result))
=== modified file 'lib/lp/registry/vocabularies.py'
--- lib/lp/registry/vocabularies.py 2012-07-27 03:57:33 +0000
+++ lib/lp/registry/vocabularies.py 2012-07-27 14:59:41 +0000
@@ -304,8 +304,9 @@
if query is None or an empty string.
"""
if query:
- query = ensure_unicode(query).lower()
- like_query = "'%%' || %s || '%%'" % quote_like(query)
+ query = ensure_unicode(query)
+ like_query = query.lower()
+ like_query = "'%%' || %s || '%%'" % quote_like(like_query)
fti_query = quote(query)
sql = "active = 't' AND (name LIKE %s OR fti @@ ftq(%s))" % (
like_query, fti_query)
@@ -354,8 +355,9 @@
if query is None or an empty string.
"""
if query:
- query = ensure_unicode(query).lower()
- like_query = "'%%' || %s || '%%'" % quote_like(query)
+ query = ensure_unicode(query)
+ like_query = query.lower()
+ like_query = "'%%' || %s || '%%'" % quote_like(like_query)
fti_query = quote(query)
sql = "active = 't' AND (name LIKE %s OR fti @@ ftq(%s))" % (
like_query, fti_query)
@@ -435,7 +437,7 @@
if not text:
return self.emptySelectResults()
- return self._select(ensure_unicode(text).lower())
+ return self._select(ensure_unicode(text))
class PersonAccountToMergeVocabulary(
@@ -469,7 +471,7 @@
if not text:
return self.emptySelectResults()
- text = ensure_unicode(text).lower()
+ text = ensure_unicode(text)
return self._select(text)
@@ -649,14 +651,15 @@
FROM (
SELECT Person.id,
(case
- when person.name=? then 100
- when person.name like ? || '%%' then 0.6
- when lower(person.displayname) like ? || '%%' then 0.5
+ when person.name=lower(?) then 100
+ when person.name like lower(?) || '%%' then 0.6
+ when lower(person.displayname) like lower(?)
+ || '%%' then 0.5
else rank(fti, ftq(?))
end) as rank
FROM Person
- WHERE Person.name LIKE ? || '%%'
- or lower(Person.displayname) LIKE ? || '%%'
+ WHERE Person.name LIKE lower(?) || '%%'
+ or lower(Person.displayname) LIKE lower(?) || '%%'
or Person.fti @@ ftq(?)
UNION ALL
SELECT Person.id, 0.8 AS rank
@@ -667,7 +670,7 @@
SELECT Person.id, 0.4 AS rank
FROM Person, EmailAddress
WHERE Person.id = EmailAddress.person
- AND LOWER(EmailAddress.email) LIKE ? || '%%'
+ AND LOWER(EmailAddress.email) LIKE lower(?) || '%%'
AND status IN (?, ?)
) AS person_match
GROUP BY id, is_private_team
@@ -680,9 +683,10 @@
private_tables = [Person] + private_tables
private_ranking_sql = SQL("""
(case
- when person.name=? then 100
- when person.name like ? || '%%' then 0.6
- when lower(person.displayname) like ? || '%%' then 0.5
+ when person.name=lower(?) then 100
+ when person.name like lower(?) || '%%' then 0.6
+ when lower(person.displayname) like lower(?)
+ || '%%' then 0.5
else rank(fti, ftq(?))
end) as rank
""", (text, text, text, text))
@@ -696,8 +700,8 @@
SQL("true as is_private_team")),
where=And(
SQL("""
- Person.name LIKE ? || '%%'
- OR lower(Person.displayname) LIKE ? || '%%'
+ Person.name LIKE lower(?) || '%%'
+ OR lower(Person.displayname) LIKE lower(?) || '%%'
OR Person.fti @@ ftq(?)
""", [text, text, text]),
private_query))
@@ -792,7 +796,7 @@
else:
return self.emptySelectResults()
- text = ensure_unicode(text).lower()
+ text = ensure_unicode(text)
return self._doSearch(text=text, vocab_filter=vocab_filter)
def searchForTerms(self, query=None, vocab_filter=None):
@@ -838,8 +842,8 @@
result = self.store.using(*tables).find(Person, query)
else:
name_match_query = SQL("""
- Person.name LIKE ? || '%%'
- OR lower(Person.displayname) LIKE ? || '%%'
+ Person.name LIKE lower(?) || '%%'
+ OR lower(Person.displayname) LIKE lower(?) || '%%'
OR Person.fti @@ ftq(?)
""", [text, text, text]),
Follow ups