launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #01630
lp:~edwin-grubbs/launchpad/bug-655802-person-vocabulary-timeout into lp:launchpad/devel
Edwin Grubbs has proposed merging lp:~edwin-grubbs/launchpad/bug-655802-person-vocabulary-timeout into lp:launchpad/devel.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
#655802 Branch:+huge-vocabulary timeout (Person and team AJAX picker fails)
https://bugs.launchpad.net/bugs/655802
Summary
-------
Fixes soft timeouts when the AJAX Picker searches the
ValidPersonOrTeamVocabulary or its subclasses.
Implementation details
----------------------
Rewrote query to remove private_inner_select subquery, which sped up the
query. Don't check the Account table, since the person record is
considered broken if the account status is active, and it doesn't have a
preferred email address. The EmailAddress is actually queried twice.
First, to search all VALIDATED and PREFERRED email addresses for certain
text. Secondly, to verify that any non-team has a PREFERRED email.
lib/lp/registry/vocabularies.py
Added test to verify that the extra_clause is getting applied to the
query in _doSearch() when that method is passed in a search term.
Previously, the the picker would allow you to select a private team
as a member of itself. Later, it would fail because the __contains__()
method constructs a different query without a search term.
lib/lp/registry/tests/test_person_vocabularies.py
Tests
-----
./bin/test -vv -t test_person_vocabularies
Demo and Q/A
------------
* Open http://launchpad.dev/~launchpad-beta-testers
* Click on the "Add member" and search for "John Arbash Meinel".
* It should not timeout.
--
https://code.launchpad.net/~edwin-grubbs/launchpad/bug-655802-person-vocabulary-timeout/+merge/38905
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~edwin-grubbs/launchpad/bug-655802-person-vocabulary-timeout into lp:launchpad/devel.
=== added file 'lib/lp/registry/tests/test_person_vocabularies.py'
--- lib/lp/registry/tests/test_person_vocabularies.py 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/tests/test_person_vocabularies.py 2010-10-20 03:32:48 +0000
@@ -0,0 +1,46 @@
+# Copyright 2010 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test the person vocabularies."""
+
+__metaclass__ = type
+
+from storm.store import Store
+from zope.schema.vocabulary import getVocabularyRegistry
+from zope.security.proxy import removeSecurityProxy
+
+from canonical.launchpad.ftests import login_person
+from canonical.testing.layers import LaunchpadFunctionalLayer
+from lp.registry.interfaces.person import PersonVisibility
+from lp.testing import TestCaseWithFactory
+
+
+class TestValidTeamMemberVocabulary(TestCaseWithFactory):
+ """Test that the ValidTeamMemberVocabulary behaves as expected."""
+
+ layer = LaunchpadFunctionalLayer
+
+ def searchVocabulary(self, team, text):
+ vocabulary_registry = getVocabularyRegistry()
+ naked_vocabulary = removeSecurityProxy(
+ vocabulary_registry.get(team, 'ValidTeamMember'))
+ return naked_vocabulary._doSearch(text)
+
+ def test_public_team_cannot_be_a_member_of_itself(self):
+ # A public team should be filtered by the vocab.extra_clause
+ # when provided a search term.
+ team_owner = self.factory.makePerson()
+ login_person(team_owner)
+ team = self.factory.makeTeam(owner=team_owner)
+ Store.of(team).flush()
+ self.assertFalse(team in self.searchVocabulary(team, team.name))
+
+ def test_private_team_cannot_be_a_member_of_itself(self):
+ # A private team should be filtered by the vocab.extra_clause
+ # when provided a search term.
+ team_owner = self.factory.makePerson()
+ login_person(team_owner)
+ team = self.factory.makeTeam(
+ owner=team_owner, visibility=PersonVisibility.PRIVATE)
+ Store.of(team).flush()
+ self.assertFalse(team in self.searchVocabulary(team, team.name))
=== modified file 'lib/lp/registry/vocabularies.py'
--- lib/lp/registry/vocabularies.py 2010-09-27 20:47:58 +0000
+++ lib/lp/registry/vocabularies.py 2010-10-20 03:32:48 +0000
@@ -94,11 +94,9 @@
SQLBase,
sqlvalues,
)
-from canonical.launchpad.database.account import Account
from canonical.launchpad.database.emailaddress import EmailAddress
from canonical.launchpad.database.stormsugar import StartsWith
from canonical.launchpad.helpers import shortlist
-from canonical.launchpad.interfaces.account import AccountStatus
from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus
from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
from canonical.launchpad.interfaces.lpstorm import IStore
@@ -109,7 +107,6 @@
IStoreSelector,
MAIN_STORE,
)
-from lp.app.browser.tales import DateTimeFormatterAPI
from canonical.launchpad.webapp.vocabulary import (
BatchedCountableIterator,
CountableIterator,
@@ -118,6 +115,7 @@
NamedSQLObjectVocabulary,
SQLObjectVocabularyBase,
)
+from lp.app.browser.tales import DateTimeFormatterAPI
from lp.blueprints.interfaces.specification import ISpecification
from lp.bugs.interfaces.bugtask import (
IBugTask,
@@ -524,16 +522,11 @@
# TeamParticipation table, which is very expensive. The search
# for private teams does need that table but the number of private
# teams is very small so the cost is not great.
- valid_email_statuses = (
- EmailAddressStatus.VALIDATED,
- EmailAddressStatus.PREFERRED,
- )
# First search for public persons and teams that match the text.
public_tables = [
Person,
LeftJoin(EmailAddress, EmailAddress.person == Person.id),
- LeftJoin(Account, EmailAddress.account == Account.id),
]
# Create an inner query that will match public persons and teams
@@ -565,10 +558,15 @@
FROM Person, EmailAddress
WHERE EmailAddress.person = Person.id
AND lower(email) LIKE ? || '%%'
+ AND EmailAddress.status IN (?, ?)
) AS public_subquery
ORDER BY rank DESC
LIMIT ?
- """, (text, text, text, text, text, self.LIMIT))
+ """, (text, text, text, text, text,
+ EmailAddressStatus.VALIDATED.value,
+ EmailAddressStatus.PREFERRED.value,
+ self.LIMIT))
+
public_result = self.store.using(*public_tables).find(
Person,
@@ -579,12 +577,9 @@
Or(# A valid person-or-team is either a team...
# Note: 'Not' due to Bug 244768.
Not(Person.teamowner == None),
-
- # Or a person who has an active account and a working
- # email address.
- And(Account.status == AccountStatus.ACTIVE,
- EmailAddress.status.is_in(valid_email_statuses))),
- self.extra_clause))
+ # Or a person who has a preferred email address.
+ EmailAddress.status == EmailAddressStatus.PREFERRED),
+ ))
# The public query doesn't need to be ordered as it will be done
# at the end.
public_result.order_by()
@@ -596,33 +591,28 @@
# Searching for private teams that match can be easier since we
# are only interested in teams. Teams can have email addresses
# but we're electing to ignore them here.
- private_inner_select = SQL("""
- SELECT Person.id
- FROM Person
- WHERE Person.fti @@ ftq(?)
- LIMIT ?
- """, (text, self.LIMIT))
private_result = self.store.using(*private_tables).find(
Person,
And(
- Person.id.is_in(private_inner_select),
+ SQL('Person.fti @@ ftq(?)', [text]),
private_query,
)
)
- # The private query doesn't need to be ordered as it will be done
- # at the end, and we need to eliminate the default ordering.
- private_result.order_by()
+ private_result.order_by(SQL('rank(fti, ftq(?)) DESC', [text]))
+ private_result.config(limit=self.LIMIT)
combined_result = public_result.union(private_result)
# Eliminate default ordering.
combined_result.order_by()
# XXX: BradCrittenden 2009-04-26 bug=217644: The use of Alias and
- # is a work-around for .count() not working with the 'distinct'
- # option.
+ # _get_select() is a work-around for .count() not working
+ # with the 'distinct' option.
subselect = Alias(combined_result._get_select(), 'Person')
exact_match = (Person.name == text)
- result = self.store.using(subselect).find((Person, exact_match))
+ result = self.store.using(subselect).find(
+ (Person, exact_match),
+ self.extra_clause)
# XXX: BradCrittenden 2009-05-07 bug=373228: A bug in Storm prevents
# setting the 'distinct' and 'limit' options in a single call to
# .config(). The work-around is to split them up. Note the limit has
@@ -755,8 +745,8 @@
Person.id NOT IN (
SELECT team FROM TeamParticipation
WHERE person = %d
- ) AND Person.id != %d
- """ % (self.team.id, self.team.id)
+ )
+ """ % self.team.id
class ValidTeamOwnerVocabulary(ValidPersonOrTeamVocabulary):