Merge lp:~wgrant/launchpad/tm-performance-4-queries into lp:launchpad

Proposed by William Grant
Status: Work in progress
Proposed branch: lp:~wgrant/launchpad/tm-performance-4-queries
Merge into: lp:launchpad
Prerequisite: lp:~wgrant/launchpad/tm-performance-2-model
Diff against target: 496 lines (+105/-210)
3 files modified
lib/lp/translations/model/pofile.py (+74/-172)
lib/lp/translations/model/potmsgset.py (+28/-36)
lib/lp/translations/tests/test_suggestions.py (+3/-2)
To merge this branch: bzr merge lp:~wgrant/launchpad/tm-performance-4-queries
Reviewer Review Type Date Requested Status
Launchpad code reviewers Pending
Review via email: mp+251345@code.launchpad.net
To post a comment you must log in.
17375. By William Grant

Drop unused functions.

17376. By William Grant

Merge bug-736005-trivialise.

17377. By William Grant

Text searches now use TTI.msgid_*.

17378. By William Grant

POFile POTMsgSet filters now use TTI denormed columns.

Unmerged revisions

17378. By William Grant

POFile POTMsgSet filters now use TTI denormed columns.

17377. By William Grant

Text searches now use TTI.msgid_*.

17376. By William Grant

Merge bug-736005-trivialise.

17375. By William Grant

Drop unused functions.

17374. By William Grant

Merge tm-performance-2-model.

17373. By William Grant

Translation suggestion queries now use the denormed columns.

17372. By William Grant

Fix POTMsgSet.suggestive clone and tests.

17371. By William Grant

Set POTemplate.suggestive based on normal suggestiveness criteria, and add POTemplateSet.filterSuggestiveTargets with the same.

17370. By William Grant

Propagate suggestive down from POTemplate to POTMsgSet to TranslationMessage.

17369. By William Grant

submitSuggestion now uses _makeTranslationMessage.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/translations/model/pofile.py'
--- lib/lp/translations/model/pofile.py 2015-02-27 22:58:59 +0000
+++ lib/lp/translations/model/pofile.py 2015-02-28 01:27:16 +0000
@@ -13,6 +13,7 @@
13 ]13 ]
1414
15import datetime15import datetime
16from operator import itemgetter
1617
17import pytz18import pytz
18from sqlobject import (19from sqlobject import (
@@ -50,6 +51,7 @@
50from lp.registry.interfaces.person import validate_public_person51from lp.registry.interfaces.person import validate_public_person
51from lp.services.database.constants import UTC_NOW52from lp.services.database.constants import UTC_NOW
52from lp.services.database.datetimecol import UtcDateTimeCol53from lp.services.database.datetimecol import UtcDateTimeCol
54from lp.services.database.decoratedresultset import DecoratedResultSet
53from lp.services.database.interfaces import (55from lp.services.database.interfaces import (
54 IMasterStore,56 IMasterStore,
55 IStore,57 IStore,
@@ -57,7 +59,6 @@
57from lp.services.database.sqlbase import (59from lp.services.database.sqlbase import (
58 flush_database_updates,60 flush_database_updates,
59 quote,61 quote,
60 quote_like,
61 SQLBase,62 SQLBase,
62 sqlvalues,63 sqlvalues,
63 )64 )
@@ -97,11 +98,9 @@
97 credits_message_str,98 credits_message_str,
98 POTMsgSet,99 POTMsgSet,
99 )100 )
101from lp.translations.model.potranslation import POTranslation
100from lp.translations.model.translationimportqueue import collect_import_info102from lp.translations.model.translationimportqueue import collect_import_info
101from lp.translations.model.translationmessage import (103from lp.translations.model.translationmessage import TranslationMessage
102 make_plurals_sql_fragment,
103 TranslationMessage,
104 )
105from lp.translations.model.translationtemplateitem import (104from lp.translations.model.translationtemplateitem import (
106 TranslationTemplateItem,105 TranslationTemplateItem,
107 )106 )
@@ -112,17 +111,6 @@
112 )111 )
113112
114113
115def compose_sql_translationmessage_has_translations(tm_sql_identifier):
116 """Compose SQL for "`TranslationMessage` is nonempty.".
117
118 :param tm_sql_identifier: The SQL identifier for the
119 `TranslationMessage` in the query that's to be tested.
120 """
121 return "COALESCE(%s) IS NOT NULL" % ", ".join([
122 "%s.msgstr%d" % (tm_sql_identifier, form)
123 for form in xrange(TranslationConstants.MAX_PLURAL_FORMS)])
124
125
126class POFileMixIn(RosettaStats):114class POFileMixIn(RosettaStats):
127 """Base class for `POFile` and `DummyPOFile`.115 """Base class for `POFile` and `DummyPOFile`.
128116
@@ -166,102 +154,6 @@
166 header.has_plural_forms = self.potemplate.hasPluralMessage()154 header.has_plural_forms = self.potemplate.hasPluralMessage()
167 return header155 return header
168156
169 def _getTranslationSearchQuery(self, pofile, plural_form, text):
170 """Query to find `text` in `plural_form` translations of a `pofile`.
171
172 This produces a list of clauses that can be used to search for
173 TranslationMessages containing `text` in their msgstr[`plural_form`].
174 Returned values are POTMsgSet ids containing them, expected to be
175 used in a UNION across all plural forms.
176 """
177 translation_match = """
178 -- Find translations containing `text`.
179 -- Like in findPOTMsgSetsContaining(), to avoid seqscans on
180 -- POTranslation table, we do ILIKE comparison on them in
181 -- a subselect which is first filtered by the POFile.
182 SELECT TranslationMessage.potmsgset
183 FROM TranslationMessage
184 JOIN TranslationTemplateItem
185 ON TranslationMessage.potmsgset
186 = TranslationTemplateItem.potmsgset
187 WHERE
188 TranslationTemplateItem.potemplate = %(potemplate)s AND
189 TranslationMessage.language = %(language)s AND
190 TranslationMessage.msgstr%(plural_form)d IN (
191 SELECT POTranslation.id FROM POTranslation WHERE
192 POTranslation.id IN (
193 SELECT DISTINCT(msgstr%(plural_form)d)
194 FROM TranslationMessage AS tm_ids
195 JOIN TranslationTemplateItem
196 ON tm_ids.potmsgset=TranslationTemplateItem.potmsgset
197 WHERE
198 TranslationTemplateItem.potemplate
199 = %(potemplate)s AND
200 TranslationTemplateItem.sequence > 0 AND
201 tm_ids.language=%(language)s
202 ) AND
203 POTranslation.translation
204 ILIKE '%%' || %(text)s || '%%')
205 """ % dict(potemplate=quote(pofile.potemplate),
206 language=quote(pofile.language),
207 plural_form=plural_form,
208 text=quote_like(text))
209 return translation_match
210
211 def _getTemplateSearchQuery(self, text):
212 """Query for finding `text` in msgids of this POFile.
213 """
214 english_match = """
215 -- Step 1a: get POTMsgSets where msgid_singular contains `text`
216 -- To avoid seqscans on POMsgID table (what LIKE usually
217 -- does), we do ILIKE comparison on them in a subselect first
218 -- filtered by this POTemplate.
219 SELECT POTMsgSet.id
220 FROM POTMsgSet
221 JOIN TranslationTemplateItem
222 ON TranslationTemplateItem.potmsgset=POTMsgSet.id AND
223 TranslationTemplateItem.potemplate=%s
224 WHERE
225 (POTMsgSet.msgid_singular IS NOT NULL AND
226 POTMsgSet.msgid_singular IN (
227 SELECT POMsgID.id FROM POMsgID
228 WHERE id IN (
229 SELECT DISTINCT(POTMsgSet.msgid_singular)
230 FROM POTMsgSet
231 JOIN TranslationTemplateItem
232 ON TranslationTemplateItem.potmsgset = POTMsgSet.id
233 WHERE
234 TranslationTemplateItem.potemplate=%s AND
235 TranslationTemplateItem.sequence > 0
236 ) AND
237 msgid ILIKE '%%' || %s || '%%'))
238 UNION
239 -- Step 1b: like above, just on msgid_plural.
240 SELECT POTMsgSet.id
241 FROM POTMsgSet
242 JOIN TranslationTemplateItem
243 ON TranslationTemplateItem.potmsgset=POTMsgSet.id AND
244 TranslationTemplateItem.potemplate=%s
245 WHERE
246 (POTMsgSet.msgid_plural IS NOT NULL AND
247 POTMsgSet.msgid_plural IN (
248 SELECT POMsgID.id FROM POMsgID
249 WHERE id IN (
250 SELECT DISTINCT(POTMsgSet.msgid_plural)
251 FROM POTMsgSet
252 JOIN TranslationTemplateItem
253 ON TranslationTemplateItem.potmsgset = POTMsgSet.id
254 WHERE
255 TranslationTemplateItem.potemplate=%s AND
256 TranslationTemplateItem.sequence > 0
257 ) AND
258 msgid ILIKE '%%' || %s || '%%'))
259 """ % (quote(self.potemplate), quote(self.potemplate),
260 quote_like(text),
261 quote(self.potemplate), quote(self.potemplate),
262 quote_like(text))
263 return english_match
264
265 def _getOrderedPOTMsgSets(self, origin_tables, query):157 def _getOrderedPOTMsgSets(self, origin_tables, query):
266 """Find all POTMsgSets matching `query` from `origin_tables`.158 """Find all POTMsgSets matching `query` from `origin_tables`.
267159
@@ -274,41 +166,79 @@
274 POTMsgSet, query)166 POTMsgSet, query)
275 return results.order_by(TranslationTemplateItem.sequence)167 return results.order_by(TranslationTemplateItem.sequence)
276168
169 def _getTranslationSearchBits(self, pofile):
170 ThisTM = ClassAlias(TranslationMessage)
171 ThisPT = ClassAlias(POTranslation)
172 str_id_cols = [
173 getattr(ThisTM, 'msgstr%dID' % plural_form)
174 for plural_form in range(pofile.plural_forms)]
175 origin = [
176 LeftJoin(
177 ThisTM,
178 And(
179 ThisTM.potmsgsetID ==
180 TranslationTemplateItem.potmsgsetID,
181 ThisTM.languageID == pofile.languageID)),
182 LeftJoin(
183 ThisPT,
184 ThisPT.id.is_in(str_id_cols)),
185 ]
186 search_options = [ThisPT.translation]
187 return origin, search_options
188
277 def findPOTMsgSetsContaining(self, text):189 def findPOTMsgSetsContaining(self, text):
278 """See `IPOFile`."""190 """See `IPOFile`."""
191 origin = [
192 POTMsgSet,
193 Join(
194 TranslationTemplateItem,
195 TranslationTemplateItem.potmsgsetID == POTMsgSet.id),
196 ]
279 clauses = [197 clauses = [
280 'TranslationTemplateItem.potemplate = %s' % sqlvalues(198 TranslationTemplateItem.potemplateID == self.potemplate.id,
281 self.potemplate),199 TranslationTemplateItem.sequence > 0,
282 'TranslationTemplateItem.potmsgset = POTMsgSet.id',
283 'TranslationTemplateItem.sequence > 0',
284 ]200 ]
285201
286 if text is not None:202 if text is not None:
287 assert len(text) > 1, (203 assert len(text) > 1, (
288 "You can not search for strings shorter than 2 characters.")204 "You can not search for strings shorter than 2 characters.")
289205
206 # A list of string columns, of which at least one must match
207 # the search term.
208 search_options = []
209
210 # Search the English strings.
290 if self.potemplate.uses_english_msgids:211 if self.potemplate.uses_english_msgids:
291 english_match = self._getTemplateSearchQuery(text)212 origin.append(Join(
213 POMsgID,
214 POMsgID.id.is_in((
215 TranslationTemplateItem.msgid_singularID,
216 TranslationTemplateItem.msgid_pluralID))))
217 search_options.extend([POMsgID.msgid])
292 else:218 else:
293 # If msgids are not in English, use English PO file219 en_po = self.potemplate.getPOFileByLang('en')
294 # to fetch original strings instead.220 en_origin, en_options = self._getTranslationSearchBits(en_po)
295 en_pofile = self.potemplate.getPOFileByLang('en')221 origin.extend(en_origin)
296 english_match = self._getTranslationSearchQuery(222 search_options.extend(en_options)
297 en_pofile, 0, text)
298223
299 # Do not look for translations in a DummyPOFile.224 # Search the translations themselves, unless this is a
300 search_clauses = [english_match]225 # DummyPOFile.
301 if self.id is not None:226 if self.id is not None:
302 for plural_form in range(self.plural_forms):227 po_origin, po_options = self._getTranslationSearchBits(self)
303 translation_match = self._getTranslationSearchQuery(228 origin.extend(po_origin)
304 self, plural_form, text)229 search_options.extend(po_options)
305 search_clauses.append(translation_match)230
306231 # Case-insensitively substring-match the search term against
307 clauses.append(232 # each searchable column.
308 "POTMsgSet.id IN (" + " UNION ".join(search_clauses) + ")")233 clauses.append(Or(*(
309234 str.like('%%%s%%' % text, case_sensitive=False)
310 return self._getOrderedPOTMsgSets(235 for str in search_options)))
311 [POTMsgSet, TranslationTemplateItem], ' AND '.join(clauses))236
237 result = IMasterStore(POTMsgSet).using(*origin).find(
238 (POTMsgSet, TranslationTemplateItem),
239 *clauses).config(distinct=True).order_by(
240 TranslationTemplateItem.sequence)
241 return DecoratedResultSet(result, itemgetter(0))
312242
313 def getFullLanguageCode(self):243 def getFullLanguageCode(self):
314 """See `IPOFile`."""244 """See `IPOFile`."""
@@ -529,25 +459,6 @@
529 "Calling prepareTranslationCredits on a message with "459 "Calling prepareTranslationCredits on a message with "
530 "unknown credits type '%s'." % credits_type.title)460 "unknown credits type '%s'." % credits_type.title)
531461
532 def _getClausesForPOFileMessages(self, current=True):
533 """Get TranslationMessages for the POFile via TranslationTemplateItem.
534
535 Call-site will have to have appropriate clauseTables.
536 """
537 # When all the code that uses this method is moved to Storm,
538 # we can replace it with _getStormClausesForPOFileMessages
539 # and then remove it.
540 clauses = [
541 'TranslationTemplateItem.potemplate = %s' % sqlvalues(
542 self.potemplate),
543 ('TranslationTemplateItem.potmsgset'
544 ' = TranslationMessage.potmsgset'),
545 'TranslationMessage.language = %s' % sqlvalues(self.language)]
546 if current:
547 clauses.append('TranslationTemplateItem.sequence > 0')
548
549 return clauses
550
551 def _getStormClausesForPOFileMessages(self, current=True):462 def _getStormClausesForPOFileMessages(self, current=True):
552 """Get TranslationMessages for the POFile via TranslationTemplateItem.463 """Get TranslationMessages for the POFile via TranslationTemplateItem.
553 """464 """
@@ -627,20 +538,10 @@
627 """See `IPOFile`."""538 """See `IPOFile`."""
628 # We get all POTMsgSet.ids with translations, and later539 # We get all POTMsgSet.ids with translations, and later
629 # exclude them using a NOT IN subselect.540 # exclude them using a NOT IN subselect.
630 translated_clauses, clause_tables = self._getTranslatedMessagesQuery()541 trans_clauses, trans_tables = self._getTranslatedMessagesQuery()
631 translated_query = Select(542 translated_query = Select(
632 POTMsgSet.id,543 TranslationTemplateItem.potmsgsetID,
633 tables=[TranslationTemplateItem, TranslationMessage, POTMsgSet],544 tables=trans_tables, where=And(*trans_clauses))
634 where=And(
635 # Even though this seems silly, Postgres prefers
636 # TranslationTemplateItem index if we add it (and on
637 # staging we get more than a 10x speed improvement: from
638 # 8s to 0.7s). We also need to put it before any other
639 # clauses to be actually useful.
640 TranslationTemplateItem.potmsgsetID ==
641 TranslationTemplateItem.potmsgsetID,
642 POTMsgSet.id == TranslationTemplateItem.potmsgsetID,
643 *translated_clauses))
644 clauses = [545 clauses = [
645 TranslationTemplateItem.potemplateID == self.potemplate.id,546 TranslationTemplateItem.potemplateID == self.potemplate.id,
646 TranslationTemplateItem.potmsgsetID == POTMsgSet.id,547 TranslationTemplateItem.potmsgsetID == POTMsgSet.id,
@@ -669,7 +570,7 @@
669 Coalesce(Diverged.date_reviewed, Diverged.date_created),570 Coalesce(Diverged.date_reviewed, Diverged.date_created),
670 tables=[Diverged],571 tables=[Diverged],
671 where=And(572 where=And(
672 Diverged.potmsgsetID == POTMsgSet.id,573 Diverged.potmsgsetID == TranslationTemplateItem.potmsgsetID,
673 Diverged.languageID == self.language.id,574 Diverged.languageID == self.language.id,
674 getattr(Diverged, flag_name),575 getattr(Diverged, flag_name),
675 Diverged.potemplateID == self.potemplate.id))576 Diverged.potemplateID == self.potemplate.id))
@@ -679,7 +580,7 @@
679 Coalesce(Shared.date_reviewed, Shared.date_created),580 Coalesce(Shared.date_reviewed, Shared.date_created),
680 tables=[Shared],581 tables=[Shared],
681 where=And(582 where=And(
682 Shared.potmsgsetID == POTMsgSet.id,583 Shared.potmsgsetID == TranslationTemplateItem.potmsgsetID,
683 Shared.languageID == self.language.id,584 Shared.languageID == self.language.id,
684 getattr(Shared, flag_name),585 getattr(Shared, flag_name),
685 Shared.potemplateID == None))586 Shared.potemplateID == None))
@@ -694,7 +595,7 @@
694 # A POT set has "new" suggestions if there is a non current595 # A POT set has "new" suggestions if there is a non current
695 # TranslationMessage newer than the current reviewed one.596 # TranslationMessage newer than the current reviewed one.
696 query = And(597 query = And(
697 POTMsgSet.id.is_in(598 TranslationTemplateItem.potmsgsetID.is_in(
698 Select(599 Select(
699 TranslationMessage.potmsgsetID,600 TranslationMessage.potmsgsetID,
700 tables=[601 tables=[
@@ -736,7 +637,7 @@
736 Diverged.potemplateID == self.potemplate.id))))637 Diverged.potemplateID == self.potemplate.id))))
737 imported_clauses = [638 imported_clauses = [
738 Imported.id != TranslationMessage.id,639 Imported.id != TranslationMessage.id,
739 Imported.potmsgsetID == POTMsgSet.id,640 Imported.potmsgsetID == TranslationTemplateItem.potmsgsetID,
740 Imported.languageID == self.language.id,641 Imported.languageID == self.language.id,
741 getattr(Imported, other_side_flag_name),642 getattr(Imported, other_side_flag_name),
742 Or(643 Or(
@@ -798,7 +699,8 @@
798 'table_name': table_name,699 'table_name': table_name,
799 } for plural_form in range(1, self.plural_forms))700 } for plural_form in range(1, self.plural_forms))
800 query.append(701 query.append(
801 '(POTMsgSet.msgid_plural IS NULL OR (%s))' % plurals_query)702 '(%(table_name)s.msgid_plural IS NULL OR (%(plurals_query)s))'
703 % {'plurals_query': plurals_query, 'table_name': table_name})
802 return query704 return query
803705
804 def _countTranslations(self):706 def _countTranslations(self):
@@ -1531,7 +1433,7 @@
1531 clauses = [1433 clauses = [
1532 TranslationTemplateItem.potemplateID == POFile.potemplateID,1434 TranslationTemplateItem.potemplateID == POFile.potemplateID,
1533 POTMsgSet.id == TranslationTemplateItem.potmsgsetID,1435 POTMsgSet.id == TranslationTemplateItem.potmsgsetID,
1534 POTMsgSet.msgid_singular == POMsgID.id,1436 TranslationTemplateItem.msgid_singular == POMsgID.id,
1535 POMsgID.msgid.is_in(POTMsgSet.credits_message_ids),1437 POMsgID.msgid.is_in(POTMsgSet.credits_message_ids),
1536 ]1438 ]
1537 if untranslated:1439 if untranslated:
15381440
=== modified file 'lib/lp/translations/model/potmsgset.py'
--- lib/lp/translations/model/potmsgset.py 2015-02-28 01:27:16 +0000
+++ lib/lp/translations/model/potmsgset.py 2015-02-28 01:27:16 +0000
@@ -21,9 +21,12 @@
21 StringCol,21 StringCol,
22 )22 )
23from storm.expr import (23from storm.expr import (
24 And,
24 Coalesce,25 Coalesce,
25 Desc,26 Desc,
27 Not,
26 Or,28 Or,
29 Select,
27 SQL,30 SQL,
28 )31 )
29from storm.store import (32from storm.store import (
@@ -389,8 +392,9 @@
389 # Watch out when changing this condition: make sure it's done in392 # Watch out when changing this condition: make sure it's done in
390 # a way so that indexes are indeed hit when the query is executed.393 # a way so that indexes are indeed hit when the query is executed.
391 # Also note that there is a NOT(in_use_clause) index.394 # Also note that there is a NOT(in_use_clause) index.
392 in_use_clause = (395 in_use_clause = Or(
393 "(is_current_ubuntu IS TRUE OR is_current_upstream IS TRUE)")396 TranslationMessage.is_current_ubuntu,
397 TranslationMessage.is_current_upstream)
394 # Present a list of language + usage constraints to sql. A language398 # Present a list of language + usage constraints to sql. A language
395 # can either be unconstrained, used, or suggested depending on which399 # can either be unconstrained, used, or suggested depending on which
396 # of suggested_languages, used_languages it appears in.400 # of suggested_languages, used_languages it appears in.
@@ -401,26 +405,16 @@
401 used_languages = used_languages - both_languages405 used_languages = used_languages - both_languages
402 lang_used = []406 lang_used = []
403 if both_languages:407 if both_languages:
404 lang_used.append('TranslationMessage.language IN %s' %408 lang_used.append(
405 quote(both_languages))409 TranslationMessage.languageID.is_in(both_languages))
406 if used_languages:410 if used_languages:
407 lang_used.append('(TranslationMessage.language IN %s AND %s)' % (411 lang_used.append(And(
408 quote(used_languages), in_use_clause))412 TranslationMessage.languageID.is_in(used_languages),
413 in_use_clause))
409 if suggested_languages:414 if suggested_languages:
410 lang_used.append(415 lang_used.append(And(
411 '(TranslationMessage.language IN %s AND NOT %s)' % (416 TranslationMessage.languageID.is_in(suggested_languages),
412 quote(suggested_languages), in_use_clause))417 Not(in_use_clause)))
413
414 msgsets = SQL('''msgsets AS (
415 SELECT POTMsgSet.id
416 FROM POTMsgSet
417 JOIN TranslationTemplateItem ON
418 TranslationTemplateItem.potmsgset = POTMsgSet.id
419 JOIN SuggestivePOTemplate ON
420 TranslationTemplateItem.potemplate =
421 SuggestivePOTemplate.potemplate
422 WHERE POTMsgSet.msgid_singular = %s and POTMsgSet.id <> %s
423 )''' % sqlvalues(self.msgid_singular, self))
424418
425 # Subquery to find the ids of TranslationMessages that are419 # Subquery to find the ids of TranslationMessages that are
426 # matching suggestions.420 # matching suggestions.
@@ -429,25 +423,23 @@
429 # excluding older messages that are identical to newer ones in423 # excluding older messages that are identical to newer ones in
430 # all translated forms. The Python code can later sort out the424 # all translated forms. The Python code can later sort out the
431 # distinct translations per form.425 # distinct translations per form.
432 msgstrs = ', '.join([426 msgstrs = [
433 'COALESCE(msgstr%d, -1)' % form427 'COALESCE(msgstr%d, -1)' % form
434 for form in xrange(TranslationConstants.MAX_PLURAL_FORMS)])428 for form in xrange(TranslationConstants.MAX_PLURAL_FORMS)]
435 ids_query_params = {429 ids_query = Select(
436 'msgstrs': msgstrs,430 TranslationMessage.id,
437 'where': '(' + ' OR '.join(lang_used) + ')',431 tables=[TranslationMessage],
438 }432 where=And(
439 ids_query = '''433 TranslationMessage.msgid_singular == self.msgid_singular,
440 SELECT DISTINCT ON (%(msgstrs)s)434 TranslationMessage.potmsgset != self.id,
441 TranslationMessage.id435 TranslationMessage.suggestive,
442 FROM TranslationMessage436 Or(*lang_used)),
443 JOIN msgsets ON msgsets.id = TranslationMessage.potmsgset437 order_by=msgstrs + [Desc(TranslationMessage.date_created)],
444 WHERE %(where)s438 distinct=msgstrs)
445 ORDER BY %(msgstrs)s, date_created DESC
446 ''' % ids_query_params
447439
448 result = IStore(TranslationMessage).with_(msgsets).find(440 result = IStore(TranslationMessage).find(
449 TranslationMessage,441 TranslationMessage,
450 TranslationMessage.id.is_in(SQL(ids_query)))442 TranslationMessage.id.is_in(ids_query))
451443
452 return shortlist(result, longest_expected=100, hardlimit=2000)444 return shortlist(result, longest_expected=100, hardlimit=2000)
453445
454446
=== modified file 'lib/lp/translations/tests/test_suggestions.py'
--- lib/lp/translations/tests/test_suggestions.py 2012-01-01 02:58:52 +0000
+++ lib/lp/translations/tests/test_suggestions.py 2015-02-28 01:27:16 +0000
@@ -162,7 +162,8 @@
162 text = "The application has exploded."162 text = "The application has exploded."
163 suggested_dutch = "De applicatie is ontploft."163 suggested_dutch = "De applicatie is ontploft."
164 now = datetime.now(timezone('UTC'))164 now = datetime.now(timezone('UTC'))
165 before = now - timedelta(1, 1, 1)165 before = now - timedelta(1, 1, 2)
166 after = now - timedelta(1, 1, 1)
166167
167 foomsg = self.factory.makePOTMsgSet(self.foo_template, text)168 foomsg = self.factory.makePOTMsgSet(self.foo_template, text)
168 barmsg = self.factory.makePOTMsgSet(self.bar_template, text)169 barmsg = self.factory.makePOTMsgSet(self.bar_template, text)
@@ -173,7 +174,7 @@
173 pofile=self.bar_nl, potmsgset=barmsg,174 pofile=self.bar_nl, potmsgset=barmsg,
174 translations={0: suggested_dutch})175 translations={0: suggested_dutch})
175 self.assertNotEqual(suggestion1, suggestion2)176 self.assertNotEqual(suggestion1, suggestion2)
176 removeSecurityProxy(suggestion1).date_created = before177 removeSecurityProxy(suggestion1).date_created = after
177 removeSecurityProxy(suggestion2).date_created = before178 removeSecurityProxy(suggestion2).date_created = before
178179
179 # When a third project, oof, contains the same translatable180 # When a third project, oof, contains the same translatable