Merge lp:~wgrant/launchpad/tm-performance-4-queries into lp:launchpad
- tm-performance-4-queries
- Merge into devel
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Launchpad code reviewers | Pending | ||
Review via email: mp+251345@code.launchpad.net |
Commit message
Description of the change
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. filterSuggestiv eTargets with the same. - 17370. By William Grant
-
Propagate suggestive down from POTemplate to POTMsgSet to TranslationMessage.
- 17369. By William Grant
-
submitSuggestion now uses _makeTranslatio
nMessage.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/translations/model/pofile.py' | |||
2 | --- lib/lp/translations/model/pofile.py 2015-02-27 22:58:59 +0000 | |||
3 | +++ lib/lp/translations/model/pofile.py 2015-02-28 01:27:16 +0000 | |||
4 | @@ -13,6 +13,7 @@ | |||
5 | 13 | ] | 13 | ] |
6 | 14 | 14 | ||
7 | 15 | import datetime | 15 | import datetime |
8 | 16 | from operator import itemgetter | ||
9 | 16 | 17 | ||
10 | 17 | import pytz | 18 | import pytz |
11 | 18 | from sqlobject import ( | 19 | from sqlobject import ( |
12 | @@ -50,6 +51,7 @@ | |||
13 | 50 | from lp.registry.interfaces.person import validate_public_person | 51 | from lp.registry.interfaces.person import validate_public_person |
14 | 51 | from lp.services.database.constants import UTC_NOW | 52 | from lp.services.database.constants import UTC_NOW |
15 | 52 | from lp.services.database.datetimecol import UtcDateTimeCol | 53 | from lp.services.database.datetimecol import UtcDateTimeCol |
16 | 54 | from lp.services.database.decoratedresultset import DecoratedResultSet | ||
17 | 53 | from lp.services.database.interfaces import ( | 55 | from lp.services.database.interfaces import ( |
18 | 54 | IMasterStore, | 56 | IMasterStore, |
19 | 55 | IStore, | 57 | IStore, |
20 | @@ -57,7 +59,6 @@ | |||
21 | 57 | from lp.services.database.sqlbase import ( | 59 | from lp.services.database.sqlbase import ( |
22 | 58 | flush_database_updates, | 60 | flush_database_updates, |
23 | 59 | quote, | 61 | quote, |
24 | 60 | quote_like, | ||
25 | 61 | SQLBase, | 62 | SQLBase, |
26 | 62 | sqlvalues, | 63 | sqlvalues, |
27 | 63 | ) | 64 | ) |
28 | @@ -97,11 +98,9 @@ | |||
29 | 97 | credits_message_str, | 98 | credits_message_str, |
30 | 98 | POTMsgSet, | 99 | POTMsgSet, |
31 | 99 | ) | 100 | ) |
32 | 101 | from lp.translations.model.potranslation import POTranslation | ||
33 | 100 | from lp.translations.model.translationimportqueue import collect_import_info | 102 | from lp.translations.model.translationimportqueue import collect_import_info |
38 | 101 | from lp.translations.model.translationmessage import ( | 103 | from lp.translations.model.translationmessage import TranslationMessage |
35 | 102 | make_plurals_sql_fragment, | ||
36 | 103 | TranslationMessage, | ||
37 | 104 | ) | ||
39 | 105 | from lp.translations.model.translationtemplateitem import ( | 104 | from lp.translations.model.translationtemplateitem import ( |
40 | 106 | TranslationTemplateItem, | 105 | TranslationTemplateItem, |
41 | 107 | ) | 106 | ) |
42 | @@ -112,17 +111,6 @@ | |||
43 | 112 | ) | 111 | ) |
44 | 113 | 112 | ||
45 | 114 | 113 | ||
46 | 115 | def compose_sql_translationmessage_has_translations(tm_sql_identifier): | ||
47 | 116 | """Compose SQL for "`TranslationMessage` is nonempty.". | ||
48 | 117 | |||
49 | 118 | :param tm_sql_identifier: The SQL identifier for the | ||
50 | 119 | `TranslationMessage` in the query that's to be tested. | ||
51 | 120 | """ | ||
52 | 121 | return "COALESCE(%s) IS NOT NULL" % ", ".join([ | ||
53 | 122 | "%s.msgstr%d" % (tm_sql_identifier, form) | ||
54 | 123 | for form in xrange(TranslationConstants.MAX_PLURAL_FORMS)]) | ||
55 | 124 | |||
56 | 125 | |||
57 | 126 | class POFileMixIn(RosettaStats): | 114 | class POFileMixIn(RosettaStats): |
58 | 127 | """Base class for `POFile` and `DummyPOFile`. | 115 | """Base class for `POFile` and `DummyPOFile`. |
59 | 128 | 116 | ||
60 | @@ -166,102 +154,6 @@ | |||
61 | 166 | header.has_plural_forms = self.potemplate.hasPluralMessage() | 154 | header.has_plural_forms = self.potemplate.hasPluralMessage() |
62 | 167 | return header | 155 | return header |
63 | 168 | 156 | ||
64 | 169 | def _getTranslationSearchQuery(self, pofile, plural_form, text): | ||
65 | 170 | """Query to find `text` in `plural_form` translations of a `pofile`. | ||
66 | 171 | |||
67 | 172 | This produces a list of clauses that can be used to search for | ||
68 | 173 | TranslationMessages containing `text` in their msgstr[`plural_form`]. | ||
69 | 174 | Returned values are POTMsgSet ids containing them, expected to be | ||
70 | 175 | used in a UNION across all plural forms. | ||
71 | 176 | """ | ||
72 | 177 | translation_match = """ | ||
73 | 178 | -- Find translations containing `text`. | ||
74 | 179 | -- Like in findPOTMsgSetsContaining(), to avoid seqscans on | ||
75 | 180 | -- POTranslation table, we do ILIKE comparison on them in | ||
76 | 181 | -- a subselect which is first filtered by the POFile. | ||
77 | 182 | SELECT TranslationMessage.potmsgset | ||
78 | 183 | FROM TranslationMessage | ||
79 | 184 | JOIN TranslationTemplateItem | ||
80 | 185 | ON TranslationMessage.potmsgset | ||
81 | 186 | = TranslationTemplateItem.potmsgset | ||
82 | 187 | WHERE | ||
83 | 188 | TranslationTemplateItem.potemplate = %(potemplate)s AND | ||
84 | 189 | TranslationMessage.language = %(language)s AND | ||
85 | 190 | TranslationMessage.msgstr%(plural_form)d IN ( | ||
86 | 191 | SELECT POTranslation.id FROM POTranslation WHERE | ||
87 | 192 | POTranslation.id IN ( | ||
88 | 193 | SELECT DISTINCT(msgstr%(plural_form)d) | ||
89 | 194 | FROM TranslationMessage AS tm_ids | ||
90 | 195 | JOIN TranslationTemplateItem | ||
91 | 196 | ON tm_ids.potmsgset=TranslationTemplateItem.potmsgset | ||
92 | 197 | WHERE | ||
93 | 198 | TranslationTemplateItem.potemplate | ||
94 | 199 | = %(potemplate)s AND | ||
95 | 200 | TranslationTemplateItem.sequence > 0 AND | ||
96 | 201 | tm_ids.language=%(language)s | ||
97 | 202 | ) AND | ||
98 | 203 | POTranslation.translation | ||
99 | 204 | ILIKE '%%' || %(text)s || '%%') | ||
100 | 205 | """ % dict(potemplate=quote(pofile.potemplate), | ||
101 | 206 | language=quote(pofile.language), | ||
102 | 207 | plural_form=plural_form, | ||
103 | 208 | text=quote_like(text)) | ||
104 | 209 | return translation_match | ||
105 | 210 | |||
106 | 211 | def _getTemplateSearchQuery(self, text): | ||
107 | 212 | """Query for finding `text` in msgids of this POFile. | ||
108 | 213 | """ | ||
109 | 214 | english_match = """ | ||
110 | 215 | -- Step 1a: get POTMsgSets where msgid_singular contains `text` | ||
111 | 216 | -- To avoid seqscans on POMsgID table (what LIKE usually | ||
112 | 217 | -- does), we do ILIKE comparison on them in a subselect first | ||
113 | 218 | -- filtered by this POTemplate. | ||
114 | 219 | SELECT POTMsgSet.id | ||
115 | 220 | FROM POTMsgSet | ||
116 | 221 | JOIN TranslationTemplateItem | ||
117 | 222 | ON TranslationTemplateItem.potmsgset=POTMsgSet.id AND | ||
118 | 223 | TranslationTemplateItem.potemplate=%s | ||
119 | 224 | WHERE | ||
120 | 225 | (POTMsgSet.msgid_singular IS NOT NULL AND | ||
121 | 226 | POTMsgSet.msgid_singular IN ( | ||
122 | 227 | SELECT POMsgID.id FROM POMsgID | ||
123 | 228 | WHERE id IN ( | ||
124 | 229 | SELECT DISTINCT(POTMsgSet.msgid_singular) | ||
125 | 230 | FROM POTMsgSet | ||
126 | 231 | JOIN TranslationTemplateItem | ||
127 | 232 | ON TranslationTemplateItem.potmsgset = POTMsgSet.id | ||
128 | 233 | WHERE | ||
129 | 234 | TranslationTemplateItem.potemplate=%s AND | ||
130 | 235 | TranslationTemplateItem.sequence > 0 | ||
131 | 236 | ) AND | ||
132 | 237 | msgid ILIKE '%%' || %s || '%%')) | ||
133 | 238 | UNION | ||
134 | 239 | -- Step 1b: like above, just on msgid_plural. | ||
135 | 240 | SELECT POTMsgSet.id | ||
136 | 241 | FROM POTMsgSet | ||
137 | 242 | JOIN TranslationTemplateItem | ||
138 | 243 | ON TranslationTemplateItem.potmsgset=POTMsgSet.id AND | ||
139 | 244 | TranslationTemplateItem.potemplate=%s | ||
140 | 245 | WHERE | ||
141 | 246 | (POTMsgSet.msgid_plural IS NOT NULL AND | ||
142 | 247 | POTMsgSet.msgid_plural IN ( | ||
143 | 248 | SELECT POMsgID.id FROM POMsgID | ||
144 | 249 | WHERE id IN ( | ||
145 | 250 | SELECT DISTINCT(POTMsgSet.msgid_plural) | ||
146 | 251 | FROM POTMsgSet | ||
147 | 252 | JOIN TranslationTemplateItem | ||
148 | 253 | ON TranslationTemplateItem.potmsgset = POTMsgSet.id | ||
149 | 254 | WHERE | ||
150 | 255 | TranslationTemplateItem.potemplate=%s AND | ||
151 | 256 | TranslationTemplateItem.sequence > 0 | ||
152 | 257 | ) AND | ||
153 | 258 | msgid ILIKE '%%' || %s || '%%')) | ||
154 | 259 | """ % (quote(self.potemplate), quote(self.potemplate), | ||
155 | 260 | quote_like(text), | ||
156 | 261 | quote(self.potemplate), quote(self.potemplate), | ||
157 | 262 | quote_like(text)) | ||
158 | 263 | return english_match | ||
159 | 264 | |||
160 | 265 | def _getOrderedPOTMsgSets(self, origin_tables, query): | 157 | def _getOrderedPOTMsgSets(self, origin_tables, query): |
161 | 266 | """Find all POTMsgSets matching `query` from `origin_tables`. | 158 | """Find all POTMsgSets matching `query` from `origin_tables`. |
162 | 267 | 159 | ||
163 | @@ -274,41 +166,79 @@ | |||
164 | 274 | POTMsgSet, query) | 166 | POTMsgSet, query) |
165 | 275 | return results.order_by(TranslationTemplateItem.sequence) | 167 | return results.order_by(TranslationTemplateItem.sequence) |
166 | 276 | 168 | ||
167 | 169 | def _getTranslationSearchBits(self, pofile): | ||
168 | 170 | ThisTM = ClassAlias(TranslationMessage) | ||
169 | 171 | ThisPT = ClassAlias(POTranslation) | ||
170 | 172 | str_id_cols = [ | ||
171 | 173 | getattr(ThisTM, 'msgstr%dID' % plural_form) | ||
172 | 174 | for plural_form in range(pofile.plural_forms)] | ||
173 | 175 | origin = [ | ||
174 | 176 | LeftJoin( | ||
175 | 177 | ThisTM, | ||
176 | 178 | And( | ||
177 | 179 | ThisTM.potmsgsetID == | ||
178 | 180 | TranslationTemplateItem.potmsgsetID, | ||
179 | 181 | ThisTM.languageID == pofile.languageID)), | ||
180 | 182 | LeftJoin( | ||
181 | 183 | ThisPT, | ||
182 | 184 | ThisPT.id.is_in(str_id_cols)), | ||
183 | 185 | ] | ||
184 | 186 | search_options = [ThisPT.translation] | ||
185 | 187 | return origin, search_options | ||
186 | 188 | |||
187 | 277 | def findPOTMsgSetsContaining(self, text): | 189 | def findPOTMsgSetsContaining(self, text): |
188 | 278 | """See `IPOFile`.""" | 190 | """See `IPOFile`.""" |
189 | 191 | origin = [ | ||
190 | 192 | POTMsgSet, | ||
191 | 193 | Join( | ||
192 | 194 | TranslationTemplateItem, | ||
193 | 195 | TranslationTemplateItem.potmsgsetID == POTMsgSet.id), | ||
194 | 196 | ] | ||
195 | 279 | clauses = [ | 197 | clauses = [ |
200 | 280 | 'TranslationTemplateItem.potemplate = %s' % sqlvalues( | 198 | TranslationTemplateItem.potemplateID == self.potemplate.id, |
201 | 281 | self.potemplate), | 199 | TranslationTemplateItem.sequence > 0, |
198 | 282 | 'TranslationTemplateItem.potmsgset = POTMsgSet.id', | ||
199 | 283 | 'TranslationTemplateItem.sequence > 0', | ||
202 | 284 | ] | 200 | ] |
203 | 285 | 201 | ||
204 | 286 | if text is not None: | 202 | if text is not None: |
205 | 287 | assert len(text) > 1, ( | 203 | assert len(text) > 1, ( |
206 | 288 | "You can not search for strings shorter than 2 characters.") | 204 | "You can not search for strings shorter than 2 characters.") |
207 | 289 | 205 | ||
208 | 206 | # A list of string columns, of which at least one must match | ||
209 | 207 | # the search term. | ||
210 | 208 | search_options = [] | ||
211 | 209 | |||
212 | 210 | # Search the English strings. | ||
213 | 290 | if self.potemplate.uses_english_msgids: | 211 | if self.potemplate.uses_english_msgids: |
215 | 291 | english_match = self._getTemplateSearchQuery(text) | 212 | origin.append(Join( |
216 | 213 | POMsgID, | ||
217 | 214 | POMsgID.id.is_in(( | ||
218 | 215 | TranslationTemplateItem.msgid_singularID, | ||
219 | 216 | TranslationTemplateItem.msgid_pluralID)))) | ||
220 | 217 | search_options.extend([POMsgID.msgid]) | ||
221 | 292 | else: | 218 | else: |
227 | 293 | # If msgids are not in English, use English PO file | 219 | en_po = self.potemplate.getPOFileByLang('en') |
228 | 294 | # to fetch original strings instead. | 220 | en_origin, en_options = self._getTranslationSearchBits(en_po) |
229 | 295 | en_pofile = self.potemplate.getPOFileByLang('en') | 221 | origin.extend(en_origin) |
230 | 296 | english_match = self._getTranslationSearchQuery( | 222 | search_options.extend(en_options) |
226 | 297 | en_pofile, 0, text) | ||
231 | 298 | 223 | ||
234 | 299 | # Do not look for translations in a DummyPOFile. | 224 | # Search the translations themselves, unless this is a |
235 | 300 | search_clauses = [english_match] | 225 | # DummyPOFile. |
236 | 301 | if self.id is not None: | 226 | if self.id is not None: |
247 | 302 | for plural_form in range(self.plural_forms): | 227 | po_origin, po_options = self._getTranslationSearchBits(self) |
248 | 303 | translation_match = self._getTranslationSearchQuery( | 228 | origin.extend(po_origin) |
249 | 304 | self, plural_form, text) | 229 | search_options.extend(po_options) |
250 | 305 | search_clauses.append(translation_match) | 230 | |
251 | 306 | 231 | # Case-insensitively substring-match the search term against | |
252 | 307 | clauses.append( | 232 | # each searchable column. |
253 | 308 | "POTMsgSet.id IN (" + " UNION ".join(search_clauses) + ")") | 233 | clauses.append(Or(*( |
254 | 309 | 234 | str.like('%%%s%%' % text, case_sensitive=False) | |
255 | 310 | return self._getOrderedPOTMsgSets( | 235 | for str in search_options))) |
256 | 311 | [POTMsgSet, TranslationTemplateItem], ' AND '.join(clauses)) | 236 | |
257 | 237 | result = IMasterStore(POTMsgSet).using(*origin).find( | ||
258 | 238 | (POTMsgSet, TranslationTemplateItem), | ||
259 | 239 | *clauses).config(distinct=True).order_by( | ||
260 | 240 | TranslationTemplateItem.sequence) | ||
261 | 241 | return DecoratedResultSet(result, itemgetter(0)) | ||
262 | 312 | 242 | ||
263 | 313 | def getFullLanguageCode(self): | 243 | def getFullLanguageCode(self): |
264 | 314 | """See `IPOFile`.""" | 244 | """See `IPOFile`.""" |
265 | @@ -529,25 +459,6 @@ | |||
266 | 529 | "Calling prepareTranslationCredits on a message with " | 459 | "Calling prepareTranslationCredits on a message with " |
267 | 530 | "unknown credits type '%s'." % credits_type.title) | 460 | "unknown credits type '%s'." % credits_type.title) |
268 | 531 | 461 | ||
269 | 532 | def _getClausesForPOFileMessages(self, current=True): | ||
270 | 533 | """Get TranslationMessages for the POFile via TranslationTemplateItem. | ||
271 | 534 | |||
272 | 535 | Call-site will have to have appropriate clauseTables. | ||
273 | 536 | """ | ||
274 | 537 | # When all the code that uses this method is moved to Storm, | ||
275 | 538 | # we can replace it with _getStormClausesForPOFileMessages | ||
276 | 539 | # and then remove it. | ||
277 | 540 | clauses = [ | ||
278 | 541 | 'TranslationTemplateItem.potemplate = %s' % sqlvalues( | ||
279 | 542 | self.potemplate), | ||
280 | 543 | ('TranslationTemplateItem.potmsgset' | ||
281 | 544 | ' = TranslationMessage.potmsgset'), | ||
282 | 545 | 'TranslationMessage.language = %s' % sqlvalues(self.language)] | ||
283 | 546 | if current: | ||
284 | 547 | clauses.append('TranslationTemplateItem.sequence > 0') | ||
285 | 548 | |||
286 | 549 | return clauses | ||
287 | 550 | |||
288 | 551 | def _getStormClausesForPOFileMessages(self, current=True): | 462 | def _getStormClausesForPOFileMessages(self, current=True): |
289 | 552 | """Get TranslationMessages for the POFile via TranslationTemplateItem. | 463 | """Get TranslationMessages for the POFile via TranslationTemplateItem. |
290 | 553 | """ | 464 | """ |
291 | @@ -627,20 +538,10 @@ | |||
292 | 627 | """See `IPOFile`.""" | 538 | """See `IPOFile`.""" |
293 | 628 | # We get all POTMsgSet.ids with translations, and later | 539 | # We get all POTMsgSet.ids with translations, and later |
294 | 629 | # exclude them using a NOT IN subselect. | 540 | # exclude them using a NOT IN subselect. |
296 | 630 | translated_clauses, clause_tables = self._getTranslatedMessagesQuery() | 541 | trans_clauses, trans_tables = self._getTranslatedMessagesQuery() |
297 | 631 | translated_query = Select( | 542 | translated_query = Select( |
310 | 632 | POTMsgSet.id, | 543 | TranslationTemplateItem.potmsgsetID, |
311 | 633 | tables=[TranslationTemplateItem, TranslationMessage, POTMsgSet], | 544 | tables=trans_tables, where=And(*trans_clauses)) |
300 | 634 | where=And( | ||
301 | 635 | # Even though this seems silly, Postgres prefers | ||
302 | 636 | # TranslationTemplateItem index if we add it (and on | ||
303 | 637 | # staging we get more than a 10x speed improvement: from | ||
304 | 638 | # 8s to 0.7s). We also need to put it before any other | ||
305 | 639 | # clauses to be actually useful. | ||
306 | 640 | TranslationTemplateItem.potmsgsetID == | ||
307 | 641 | TranslationTemplateItem.potmsgsetID, | ||
308 | 642 | POTMsgSet.id == TranslationTemplateItem.potmsgsetID, | ||
309 | 643 | *translated_clauses)) | ||
312 | 644 | clauses = [ | 545 | clauses = [ |
313 | 645 | TranslationTemplateItem.potemplateID == self.potemplate.id, | 546 | TranslationTemplateItem.potemplateID == self.potemplate.id, |
314 | 646 | TranslationTemplateItem.potmsgsetID == POTMsgSet.id, | 547 | TranslationTemplateItem.potmsgsetID == POTMsgSet.id, |
315 | @@ -669,7 +570,7 @@ | |||
316 | 669 | Coalesce(Diverged.date_reviewed, Diverged.date_created), | 570 | Coalesce(Diverged.date_reviewed, Diverged.date_created), |
317 | 670 | tables=[Diverged], | 571 | tables=[Diverged], |
318 | 671 | where=And( | 572 | where=And( |
320 | 672 | Diverged.potmsgsetID == POTMsgSet.id, | 573 | Diverged.potmsgsetID == TranslationTemplateItem.potmsgsetID, |
321 | 673 | Diverged.languageID == self.language.id, | 574 | Diverged.languageID == self.language.id, |
322 | 674 | getattr(Diverged, flag_name), | 575 | getattr(Diverged, flag_name), |
323 | 675 | Diverged.potemplateID == self.potemplate.id)) | 576 | Diverged.potemplateID == self.potemplate.id)) |
324 | @@ -679,7 +580,7 @@ | |||
325 | 679 | Coalesce(Shared.date_reviewed, Shared.date_created), | 580 | Coalesce(Shared.date_reviewed, Shared.date_created), |
326 | 680 | tables=[Shared], | 581 | tables=[Shared], |
327 | 681 | where=And( | 582 | where=And( |
329 | 682 | Shared.potmsgsetID == POTMsgSet.id, | 583 | Shared.potmsgsetID == TranslationTemplateItem.potmsgsetID, |
330 | 683 | Shared.languageID == self.language.id, | 584 | Shared.languageID == self.language.id, |
331 | 684 | getattr(Shared, flag_name), | 585 | getattr(Shared, flag_name), |
332 | 685 | Shared.potemplateID == None)) | 586 | Shared.potemplateID == None)) |
333 | @@ -694,7 +595,7 @@ | |||
334 | 694 | # A POT set has "new" suggestions if there is a non current | 595 | # A POT set has "new" suggestions if there is a non current |
335 | 695 | # TranslationMessage newer than the current reviewed one. | 596 | # TranslationMessage newer than the current reviewed one. |
336 | 696 | query = And( | 597 | query = And( |
338 | 697 | POTMsgSet.id.is_in( | 598 | TranslationTemplateItem.potmsgsetID.is_in( |
339 | 698 | Select( | 599 | Select( |
340 | 699 | TranslationMessage.potmsgsetID, | 600 | TranslationMessage.potmsgsetID, |
341 | 700 | tables=[ | 601 | tables=[ |
342 | @@ -736,7 +637,7 @@ | |||
343 | 736 | Diverged.potemplateID == self.potemplate.id)))) | 637 | Diverged.potemplateID == self.potemplate.id)))) |
344 | 737 | imported_clauses = [ | 638 | imported_clauses = [ |
345 | 738 | Imported.id != TranslationMessage.id, | 639 | Imported.id != TranslationMessage.id, |
347 | 739 | Imported.potmsgsetID == POTMsgSet.id, | 640 | Imported.potmsgsetID == TranslationTemplateItem.potmsgsetID, |
348 | 740 | Imported.languageID == self.language.id, | 641 | Imported.languageID == self.language.id, |
349 | 741 | getattr(Imported, other_side_flag_name), | 642 | getattr(Imported, other_side_flag_name), |
350 | 742 | Or( | 643 | Or( |
351 | @@ -798,7 +699,8 @@ | |||
352 | 798 | 'table_name': table_name, | 699 | 'table_name': table_name, |
353 | 799 | } for plural_form in range(1, self.plural_forms)) | 700 | } for plural_form in range(1, self.plural_forms)) |
354 | 800 | query.append( | 701 | query.append( |
356 | 801 | '(POTMsgSet.msgid_plural IS NULL OR (%s))' % plurals_query) | 702 | '(%(table_name)s.msgid_plural IS NULL OR (%(plurals_query)s))' |
357 | 703 | % {'plurals_query': plurals_query, 'table_name': table_name}) | ||
358 | 802 | return query | 704 | return query |
359 | 803 | 705 | ||
360 | 804 | def _countTranslations(self): | 706 | def _countTranslations(self): |
361 | @@ -1531,7 +1433,7 @@ | |||
362 | 1531 | clauses = [ | 1433 | clauses = [ |
363 | 1532 | TranslationTemplateItem.potemplateID == POFile.potemplateID, | 1434 | TranslationTemplateItem.potemplateID == POFile.potemplateID, |
364 | 1533 | POTMsgSet.id == TranslationTemplateItem.potmsgsetID, | 1435 | POTMsgSet.id == TranslationTemplateItem.potmsgsetID, |
366 | 1534 | POTMsgSet.msgid_singular == POMsgID.id, | 1436 | TranslationTemplateItem.msgid_singular == POMsgID.id, |
367 | 1535 | POMsgID.msgid.is_in(POTMsgSet.credits_message_ids), | 1437 | POMsgID.msgid.is_in(POTMsgSet.credits_message_ids), |
368 | 1536 | ] | 1438 | ] |
369 | 1537 | if untranslated: | 1439 | if untranslated: |
370 | 1538 | 1440 | ||
371 | === modified file 'lib/lp/translations/model/potmsgset.py' | |||
372 | --- lib/lp/translations/model/potmsgset.py 2015-02-28 01:27:16 +0000 | |||
373 | +++ lib/lp/translations/model/potmsgset.py 2015-02-28 01:27:16 +0000 | |||
374 | @@ -21,9 +21,12 @@ | |||
375 | 21 | StringCol, | 21 | StringCol, |
376 | 22 | ) | 22 | ) |
377 | 23 | from storm.expr import ( | 23 | from storm.expr import ( |
378 | 24 | And, | ||
379 | 24 | Coalesce, | 25 | Coalesce, |
380 | 25 | Desc, | 26 | Desc, |
381 | 27 | Not, | ||
382 | 26 | Or, | 28 | Or, |
383 | 29 | Select, | ||
384 | 27 | SQL, | 30 | SQL, |
385 | 28 | ) | 31 | ) |
386 | 29 | from storm.store import ( | 32 | from storm.store import ( |
387 | @@ -389,8 +392,9 @@ | |||
388 | 389 | # Watch out when changing this condition: make sure it's done in | 392 | # Watch out when changing this condition: make sure it's done in |
389 | 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. |
390 | 391 | # Also note that there is a NOT(in_use_clause) index. | 394 | # Also note that there is a NOT(in_use_clause) index. |
393 | 392 | in_use_clause = ( | 395 | in_use_clause = Or( |
394 | 393 | "(is_current_ubuntu IS TRUE OR is_current_upstream IS TRUE)") | 396 | TranslationMessage.is_current_ubuntu, |
395 | 397 | TranslationMessage.is_current_upstream) | ||
396 | 394 | # Present a list of language + usage constraints to sql. A language | 398 | # Present a list of language + usage constraints to sql. A language |
397 | 395 | # can either be unconstrained, used, or suggested depending on which | 399 | # can either be unconstrained, used, or suggested depending on which |
398 | 396 | # of suggested_languages, used_languages it appears in. | 400 | # of suggested_languages, used_languages it appears in. |
399 | @@ -401,26 +405,16 @@ | |||
400 | 401 | used_languages = used_languages - both_languages | 405 | used_languages = used_languages - both_languages |
401 | 402 | lang_used = [] | 406 | lang_used = [] |
402 | 403 | if both_languages: | 407 | if both_languages: |
405 | 404 | lang_used.append('TranslationMessage.language IN %s' % | 408 | lang_used.append( |
406 | 405 | quote(both_languages)) | 409 | TranslationMessage.languageID.is_in(both_languages)) |
407 | 406 | if used_languages: | 410 | if used_languages: |
410 | 407 | lang_used.append('(TranslationMessage.language IN %s AND %s)' % ( | 411 | lang_used.append(And( |
411 | 408 | quote(used_languages), in_use_clause)) | 412 | TranslationMessage.languageID.is_in(used_languages), |
412 | 413 | in_use_clause)) | ||
413 | 409 | if suggested_languages: | 414 | if suggested_languages: |
428 | 410 | lang_used.append( | 415 | lang_used.append(And( |
429 | 411 | '(TranslationMessage.language IN %s AND NOT %s)' % ( | 416 | TranslationMessage.languageID.is_in(suggested_languages), |
430 | 412 | quote(suggested_languages), in_use_clause)) | 417 | Not(in_use_clause))) |
417 | 413 | |||
418 | 414 | msgsets = SQL('''msgsets AS ( | ||
419 | 415 | SELECT POTMsgSet.id | ||
420 | 416 | FROM POTMsgSet | ||
421 | 417 | JOIN TranslationTemplateItem ON | ||
422 | 418 | TranslationTemplateItem.potmsgset = POTMsgSet.id | ||
423 | 419 | JOIN SuggestivePOTemplate ON | ||
424 | 420 | TranslationTemplateItem.potemplate = | ||
425 | 421 | SuggestivePOTemplate.potemplate | ||
426 | 422 | WHERE POTMsgSet.msgid_singular = %s and POTMsgSet.id <> %s | ||
427 | 423 | )''' % sqlvalues(self.msgid_singular, self)) | ||
431 | 424 | 418 | ||
432 | 425 | # Subquery to find the ids of TranslationMessages that are | 419 | # Subquery to find the ids of TranslationMessages that are |
433 | 426 | # matching suggestions. | 420 | # matching suggestions. |
434 | @@ -429,25 +423,23 @@ | |||
435 | 429 | # excluding older messages that are identical to newer ones in | 423 | # excluding older messages that are identical to newer ones in |
436 | 430 | # all translated forms. The Python code can later sort out the | 424 | # all translated forms. The Python code can later sort out the |
437 | 431 | # distinct translations per form. | 425 | # distinct translations per form. |
439 | 432 | msgstrs = ', '.join([ | 426 | msgstrs = [ |
440 | 433 | 'COALESCE(msgstr%d, -1)' % form | 427 | 'COALESCE(msgstr%d, -1)' % form |
454 | 434 | for form in xrange(TranslationConstants.MAX_PLURAL_FORMS)]) | 428 | for form in xrange(TranslationConstants.MAX_PLURAL_FORMS)] |
455 | 435 | ids_query_params = { | 429 | ids_query = Select( |
456 | 436 | 'msgstrs': msgstrs, | 430 | TranslationMessage.id, |
457 | 437 | 'where': '(' + ' OR '.join(lang_used) + ')', | 431 | tables=[TranslationMessage], |
458 | 438 | } | 432 | where=And( |
459 | 439 | ids_query = ''' | 433 | TranslationMessage.msgid_singular == self.msgid_singular, |
460 | 440 | SELECT DISTINCT ON (%(msgstrs)s) | 434 | TranslationMessage.potmsgset != self.id, |
461 | 441 | TranslationMessage.id | 435 | TranslationMessage.suggestive, |
462 | 442 | FROM TranslationMessage | 436 | Or(*lang_used)), |
463 | 443 | JOIN msgsets ON msgsets.id = TranslationMessage.potmsgset | 437 | order_by=msgstrs + [Desc(TranslationMessage.date_created)], |
464 | 444 | WHERE %(where)s | 438 | distinct=msgstrs) |
452 | 445 | ORDER BY %(msgstrs)s, date_created DESC | ||
453 | 446 | ''' % ids_query_params | ||
465 | 447 | 439 | ||
467 | 448 | result = IStore(TranslationMessage).with_(msgsets).find( | 440 | result = IStore(TranslationMessage).find( |
468 | 449 | TranslationMessage, | 441 | TranslationMessage, |
470 | 450 | TranslationMessage.id.is_in(SQL(ids_query))) | 442 | TranslationMessage.id.is_in(ids_query)) |
471 | 451 | 443 | ||
472 | 452 | return shortlist(result, longest_expected=100, hardlimit=2000) | 444 | return shortlist(result, longest_expected=100, hardlimit=2000) |
473 | 453 | 445 | ||
474 | 454 | 446 | ||
475 | === modified file 'lib/lp/translations/tests/test_suggestions.py' | |||
476 | --- lib/lp/translations/tests/test_suggestions.py 2012-01-01 02:58:52 +0000 | |||
477 | +++ lib/lp/translations/tests/test_suggestions.py 2015-02-28 01:27:16 +0000 | |||
478 | @@ -162,7 +162,8 @@ | |||
479 | 162 | text = "The application has exploded." | 162 | text = "The application has exploded." |
480 | 163 | suggested_dutch = "De applicatie is ontploft." | 163 | suggested_dutch = "De applicatie is ontploft." |
481 | 164 | now = datetime.now(timezone('UTC')) | 164 | now = datetime.now(timezone('UTC')) |
483 | 165 | before = now - timedelta(1, 1, 1) | 165 | before = now - timedelta(1, 1, 2) |
484 | 166 | after = now - timedelta(1, 1, 1) | ||
485 | 166 | 167 | ||
486 | 167 | foomsg = self.factory.makePOTMsgSet(self.foo_template, text) | 168 | foomsg = self.factory.makePOTMsgSet(self.foo_template, text) |
487 | 168 | barmsg = self.factory.makePOTMsgSet(self.bar_template, text) | 169 | barmsg = self.factory.makePOTMsgSet(self.bar_template, text) |
488 | @@ -173,7 +174,7 @@ | |||
489 | 173 | pofile=self.bar_nl, potmsgset=barmsg, | 174 | pofile=self.bar_nl, potmsgset=barmsg, |
490 | 174 | translations={0: suggested_dutch}) | 175 | translations={0: suggested_dutch}) |
491 | 175 | self.assertNotEqual(suggestion1, suggestion2) | 176 | self.assertNotEqual(suggestion1, suggestion2) |
493 | 176 | removeSecurityProxy(suggestion1).date_created = before | 177 | removeSecurityProxy(suggestion1).date_created = after |
494 | 177 | removeSecurityProxy(suggestion2).date_created = before | 178 | removeSecurityProxy(suggestion2).date_created = before |
495 | 178 | 179 | ||
496 | 179 | # When a third project, oof, contains the same translatable | 180 | # When a third project, oof, contains the same translatable |