Merge lp:~wgrant/launchpad/bug-728174 into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 12540
Proposed branch: lp:~wgrant/launchpad/bug-728174
Merge into: lp:launchpad
Diff against target: 287 lines (+161/-40)
3 files modified
lib/lp/services/worlddata/interfaces/language.py (+5/-3)
lib/lp/services/worlddata/model/language.py (+96/-34)
lib/lp/services/worlddata/tests/test_language.py (+60/-3)
To merge this branch: bzr merge lp:~wgrant/launchpad/bug-728174
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+52374@code.launchpad.net

Commit message

[r=lifeless][bug=728174] Optionally preload Language.translators_count in LanguageSet.get(All|Default)Languages.

Description of the change

Webservice Language collections returned by LanguageSet were timing out on repeated heavy queries to calculate Language.translators_count. This branch calculates them all in a single query, running only slightly slower than a single individual query.

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

ids = [language.id for language in languages]

I would setify this. Possibly not needed, but belt and braces.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/services/worlddata/interfaces/language.py'
--- lib/lp/services/worlddata/interfaces/language.py 2010-12-02 16:13:51 +0000
+++ lib/lp/services/worlddata/interfaces/language.py 2011-03-07 07:51:04 +0000
@@ -19,6 +19,7 @@
19 )19 )
20from lazr.lifecycle.snapshot import doNotSnapshot20from lazr.lifecycle.snapshot import doNotSnapshot
21from lazr.restful.declarations import (21from lazr.restful.declarations import (
22 call_with,
22 collection_default_content,23 collection_default_content,
23 export_as_webservice_collection,24 export_as_webservice_collection,
24 export_as_webservice_entry,25 export_as_webservice_entry,
@@ -176,11 +177,12 @@
176177
177 @export_read_operation()178 @export_read_operation()
178 @operation_returns_collection_of(ILanguage)179 @operation_returns_collection_of(ILanguage)
179 def getAllLanguages():180 @call_with(want_translators_count=True)
181 def getAllLanguages(want_translators_count=False):
180 """Return a result set of all ILanguages from Launchpad."""182 """Return a result set of all ILanguages from Launchpad."""
181183
182 @collection_default_content()184 @collection_default_content(want_translators_count=True)
183 def getDefaultLanguages():185 def getDefaultLanguages(want_translators_count=False):
184 """An API wrapper for `common_languages`"""186 """An API wrapper for `common_languages`"""
185187
186 common_languages = Attribute(188 common_languages = Attribute(
187189
=== modified file 'lib/lp/services/worlddata/model/language.py'
--- lib/lp/services/worlddata/model/language.py 2010-12-02 16:13:51 +0000
+++ lib/lp/services/worlddata/model/language.py 2011-03-07 07:51:04 +0000
@@ -1,5 +1,6 @@
1# Copyright 2009-2010 Canonical Ltd. This software is licensed under the1# Copyright 2009-2010 Canonical Ltd. This software is licensed under
2# GNU Affero General Public License version 3 (see the file LICENSE).2# the GNU Affero General Public License version 3 (see the file
3# LICENSE).
34
4# pylint: disable-msg=E0611,W02125# pylint: disable-msg=E0611,W0212
56
@@ -17,17 +18,35 @@
17 SQLRelatedJoin,18 SQLRelatedJoin,
18 StringCol,19 StringCol,
19 )20 )
20from storm.locals import Or21from storm.expr import (
22 And,
23 Count,
24 Desc,
25 Join,
26 LeftJoin,
27 Or,
28 )
21from zope.interface import implements29from zope.interface import implements
2230
23from canonical.database.enumcol import EnumCol31from canonical.database.enumcol import EnumCol
24from canonical.database.sqlbase import (32from canonical.database.sqlbase import SQLBase
25 SQLBase,33from canonical.launchpad.components.decoratedresultset import (
26 sqlvalues,34 DecoratedResultSet,
27 )35 )
28from canonical.launchpad.helpers import ensure_unicode36from canonical.launchpad.helpers import ensure_unicode
29from canonical.launchpad.interfaces.lpstorm import ISlaveStore37from canonical.launchpad.interfaces.lpstorm import (
38 ISlaveStore,
39 IStore,
40 )
30from lp.app.errors import NotFoundError41from lp.app.errors import NotFoundError
42from lp.registry.model.karma import (
43 KarmaCache,
44 KarmaCategory,
45 )
46from lp.services.propertycache import (
47 cachedproperty,
48 get_property_cache,
49 )
31from lp.services.worlddata.interfaces.language import (50from lp.services.worlddata.interfaces.language import (
32 ILanguage,51 ILanguage,
33 ILanguageSet,52 ILanguageSet,
@@ -39,6 +58,7 @@
39from lp.services.worlddata.model.spokenin import SpokenIn58from lp.services.worlddata.model.spokenin import SpokenIn
40SpokenIn59SpokenIn
4160
61
42class Language(SQLBase):62class Language(SQLBase):
43 implements(ILanguage)63 implements(ILanguage)
4464
@@ -140,34 +160,51 @@
140 @property160 @property
141 def translators(self):161 def translators(self):
142 """See `ILanguage`."""162 """See `ILanguage`."""
163 from lp.registry.model.person import (
164 Person,
165 PersonLanguage,
166 )
167 return IStore(Language).using(
168 Join(
169 Person,
170 LanguageSet._getTranslatorJoins(),
171 Person.id == PersonLanguage.personID),
172 ).find(
173 Person,
174 PersonLanguage.language == self,
175 ).order_by(Desc(KarmaCache.karmavalue))
176
177 @cachedproperty
178 def translators_count(self):
179 """See `ILanguage`."""
180 return self.translators.count()
181
182
183class LanguageSet:
184 implements(ILanguageSet)
185
186 @staticmethod
187 def _getTranslatorJoins():
143 # XXX CarlosPerelloMarin 2007-03-31 bug=102257:188 # XXX CarlosPerelloMarin 2007-03-31 bug=102257:
144 # The KarmaCache table doesn't have a field to store karma per189 # The KarmaCache table doesn't have a field to store karma per
145 # language, so we are actually returning the people with the most190 # language, so we are actually returning the people with the most
146 # translation karma that have this language selected in their191 # translation karma that have this language selected in their
147 # preferences.192 # preferences.
148 from lp.registry.model.person import Person193 from lp.registry.model.person import PersonLanguage
149 return Person.select('''194 return Join(
150 PersonLanguage.person = Person.id AND195 PersonLanguage,
151 PersonLanguage.language = %s AND196 Join(
152 KarmaCache.person = Person.id AND197 KarmaCache,
153 KarmaCache.product IS NULL AND198 KarmaCategory,
154 KarmaCache.project IS NULL AND199 And(
155 KarmaCache.sourcepackagename IS NULL AND200 KarmaCategory.name == 'translations',
156 KarmaCache.distribution IS NULL AND201 KarmaCache.categoryID == KarmaCategory.id,
157 KarmaCache.category = KarmaCategory.id AND202 KarmaCache.productID == None,
158 KarmaCategory.name = 'translations'203 KarmaCache.projectID == None,
159 ''' % sqlvalues(self), orderBy=['-KarmaCache.karmavalue'],204 KarmaCache.sourcepackagenameID == None,
160 clauseTables=[205 KarmaCache.distributionID == None)),
161 'PersonLanguage', 'KarmaCache', 'KarmaCategory'])206 PersonLanguage.personID ==
162207 KarmaCache.personID)
163 @property
164 def translators_count(self):
165 """See `ILanguage`."""
166 return self.translators.count()
167
168
169class LanguageSet:
170 implements(ILanguageSet)
171208
172 @property209 @property
173 def _visible_languages(self):210 def _visible_languages(self):
@@ -180,14 +217,39 @@
180 """See `ILanguageSet`."""217 """See `ILanguageSet`."""
181 return iter(self._visible_languages)218 return iter(self._visible_languages)
182219
183 def getDefaultLanguages(self):220 def getDefaultLanguages(self, want_translators_count=False):
184 """See `ILanguageSet`."""221 """See `ILanguageSet`."""
185 return self._visible_languages222 return self.getAllLanguages(
223 want_translators_count=want_translators_count,
224 only_visible=True)
186225
187 def getAllLanguages(self):226 def getAllLanguages(self, want_translators_count=False,
227 only_visible=False):
188 """See `ILanguageSet`."""228 """See `ILanguageSet`."""
189 return ISlaveStore(Language).find(Language).order_by(229 result = IStore(Language).find(
230 Language,
231 Language.visible == True if only_visible else True,
232 ).order_by(
190 Language.englishname)233 Language.englishname)
234 if want_translators_count:
235 def preload_translators_count(languages):
236 from lp.registry.model.person import PersonLanguage
237 ids = set(language.id for language in languages).difference(
238 set([None]))
239 counts = IStore(Language).using(
240 LeftJoin(
241 Language,
242 self._getTranslatorJoins(),
243 PersonLanguage.languageID == Language.id),
244 ).find(
245 (Language, Count(PersonLanguage)),
246 Language.id.is_in(ids),
247 ).group_by(Language)
248 for language, count in counts:
249 get_property_cache(language).translators_count = count
250 return DecoratedResultSet(
251 result, pre_iter_hook=preload_translators_count)
252 return result
191253
192 def __iter__(self):254 def __iter__(self):
193 """See `ILanguageSet`."""255 """See `ILanguageSet`."""
194256
=== modified file 'lib/lp/services/worlddata/tests/test_language.py'
--- lib/lp/services/worlddata/tests/test_language.py 2010-12-02 16:13:51 +0000
+++ lib/lp/services/worlddata/tests/test_language.py 2011-03-07 07:51:04 +0000
@@ -4,10 +4,26 @@
4__metaclass__ = type4__metaclass__ = type
55
6from lazr.lifecycle.interfaces import IDoNotSnapshot6from lazr.lifecycle.interfaces import IDoNotSnapshot
7from testtools.matchers import Equals
8from zope.component import getUtility
79
8from canonical.testing import DatabaseFunctionalLayer10from canonical.launchpad.interfaces.lpstorm import IStore
9from lp.services.worlddata.interfaces.language import ILanguage11from canonical.testing import (
10from lp.testing import TestCaseWithFactory12 DatabaseFunctionalLayer,
13 LaunchpadZopelessLayer,
14 )
15from lp.registry.interfaces.karma import IKarmaCacheManager
16from lp.registry.model.karma import KarmaCategory
17from lp.services.worlddata.interfaces.language import (
18 ILanguage,
19 ILanguageSet,
20 )
21from lp.testing import (
22 TestCaseWithFactory,
23 StormStatementRecorder,
24 )
25from lp.testing.dbuser import dbuser
26from lp.testing.matchers import HasQueryCount
1127
1228
13class TestLanguageWebservice(TestCaseWithFactory):29class TestLanguageWebservice(TestCaseWithFactory):
@@ -29,3 +45,44 @@
29 def test_guessed_pluralforms_knows(self):45 def test_guessed_pluralforms_knows(self):
30 language = self.factory.makeLanguage(pluralforms=3)46 language = self.factory.makeLanguage(pluralforms=3)
31 self.assertEqual(language.pluralforms, language.guessed_pluralforms)47 self.assertEqual(language.pluralforms, language.guessed_pluralforms)
48
49
50class TestTranslatorsCounts(TestCaseWithFactory):
51 """Test preloading of Language.translators_counts."""
52
53 layer = LaunchpadZopelessLayer
54
55 def setUp(self):
56 super(TestTranslatorsCounts, self).setUp()
57 self.translated_lang = self.factory.makeLanguage(pluralforms=None)
58 self.untranslated_lang = self.factory.makeLanguage(pluralforms=None)
59 for i in range(3):
60 translator = self.factory.makePerson()
61 translator.addLanguage(self.translated_lang)
62 with dbuser('karma'):
63 translations_category = IStore(KarmaCategory).find(
64 KarmaCategory, name='translations').one()
65 getUtility(IKarmaCacheManager).new(
66 person_id=translator.id,
67 category_id=translations_category.id,
68 value=100)
69
70 def test_translators_count(self):
71 # translators_count works.
72 self.assertEquals(3, self.translated_lang.translators_count)
73 self.assertEquals(0, self.untranslated_lang.translators_count)
74
75 def test_translators_count_queries(self):
76 # translators_count issues a single query.
77 with StormStatementRecorder() as recorder:
78 self.translated_lang.translators_count
79 self.assertThat(recorder, HasQueryCount(Equals(1)))
80
81 def test_getAllLanguages_can_preload_translators_count(self):
82 # LanguageSet.getAllLanguages() can preload translators_count.
83 list(getUtility(ILanguageSet).getAllLanguages(
84 want_translators_count=True))
85 with StormStatementRecorder() as recorder:
86 self.assertEquals(3, self.translated_lang.translators_count)
87 self.assertEquals(0, self.untranslated_lang.translators_count)
88 self.assertThat(recorder, HasQueryCount(Equals(0)))