Colin Watson has proposed merging ~cjwatson/launchpad:stormify-potmsgset-queries into launchpad:master.
Commit message: Convert queries in lp.translations.model.potmsgset to Storm Requested reviews: Launchpad code reviewers (launchpad-reviewers) For more details, see: https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/394757 -- Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:stormify-potmsgset-queries into launchpad:master.
diff --git a/lib/lp/translations/model/potmsgset.py b/lib/lp/translations/model/potmsgset.py index f256384..649f6bd 100644 --- a/lib/lp/translations/model/potmsgset.py +++ b/lib/lp/translations/model/potmsgset.py @@ -1,4 +1,4 @@ -# Copyright 2009-2018 Canonical Ltd. This software is licensed under the +# Copyright 2009-2020 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). __metaclass__ = type @@ -21,10 +21,17 @@ from sqlobject import ( StringCol, ) from storm.expr import ( + And, Coalesce, + Column, Desc, + Join, + Not, Or, + Select, SQL, + Table, + With, ) from storm.store import ( EmptyResultSet, @@ -39,11 +46,14 @@ from lp.services.config import config from lp.services.database.constants import DEFAULT from lp.services.database.interfaces import IStore from lp.services.database.sqlbase import ( - cursor, - quote, SQLBase, sqlvalues, ) +from lp.services.database.stormexpr import ( + IsTrue, + NullsFirst, + NullsLast, + ) from lp.services.helpers import shortlist from lp.services.propertycache import get_property_cache from lp.translations.interfaces.potmsgset import ( @@ -55,9 +65,6 @@ from lp.translations.interfaces.side import ( ITranslationSideTraitsSet, TranslationSide, ) -from lp.translations.interfaces.translationfileformat import ( - TranslationFileFormat, - ) from lp.translations.interfaces.translationimporter import ( ITranslationImporter, ) @@ -163,6 +170,8 @@ class POTMsgSet(SQLBase): of all the source_file_format values. Otherwise, it should be a `TranslationFileFormat` value. """ + # Circular import. + from lp.translations.model.potemplate import POTemplate translation_importer = getUtility(ITranslationImporter) @@ -175,19 +184,18 @@ class POTMsgSet(SQLBase): # Now let's find all the source_file_formats for all the # POTemplates this POTMsgSet is part of. - query = """ - SELECT DISTINCT POTemplate.source_file_format - FROM TranslationTemplateItem - JOIN POTemplate - ON POTemplate.id = TranslationTemplateItem.potemplate - WHERE TranslationTemplateItem.potmsgset = %s""" % ( - sqlvalues(self)) - cur = cursor() - cur.execute(query) - source_file_formats = cur.fetchall() - for source_file_format, in source_file_formats: + origin = [ + TranslationTemplateItem, + Join( + POTemplate, + TranslationTemplateItem.potemplate == POTemplate.id), + ] + source_file_formats = IStore(POTemplate).using(*origin).find( + POTemplate.source_file_format, + TranslationTemplateItem.potmsgset == self).config(distinct=True) + for source_file_format in source_file_formats: format = translation_importer.getTranslationFormatImporter( - TranslationFileFormat.items[source_file_format]) + source_file_format) format_uses_english_msgids = not format.uses_source_string_msgids if uses_english_msgids is None: @@ -326,15 +334,13 @@ class POTMsgSet(SQLBase): include_dismissed=False, include_unreviewed=True): """See `IPOTMsgSet`.""" - query = """ - is_current_ubuntu IS NOT TRUE AND - is_current_upstream IS NOT TRUE AND - potmsgset = %s AND - language = %s - """ % sqlvalues(self, language) - msgstr_clause = make_plurals_sql_fragment( - "msgstr%(form)d IS NOT NULL", "OR") - query += " AND (%s)" % msgstr_clause + clauses = [ + Not(IsTrue(TranslationMessage.is_current_ubuntu)), + Not(IsTrue(TranslationMessage.is_current_upstream)), + TranslationMessage.potmsgset == self, + TranslationMessage.language == language, + SQL(make_plurals_sql_fragment("msgstr%(form)d IS NOT NULL", "OR")), + ] if include_dismissed != include_unreviewed: current = self.getCurrentTranslation( potemplate, language, potemplate.translation_side) @@ -344,10 +350,10 @@ class POTMsgSet(SQLBase): else: comparing_date = current.date_reviewed if include_unreviewed: - term = " AND date_created > %s" + clause = TranslationMessage.date_created > comparing_date else: - term = " AND date_created <= %s" - query += term % sqlvalues(comparing_date) + clause = TranslationMessage.date_created <= comparing_date + clauses.append(clause) elif include_dismissed and include_unreviewed: # Return all messages pass @@ -355,7 +361,7 @@ class POTMsgSet(SQLBase): # No need to run a query. return EmptyResultSet() - return TranslationMessage.select(query) + return IStore(TranslationMessage).find(TranslationMessage, *clauses) def _getExternalTranslationMessages(self, suggested_languages=(), used_languages=()): @@ -385,8 +391,9 @@ class POTMsgSet(SQLBase): # Watch out when changing this condition: make sure it's done in # a way so that indexes are indeed hit when the query is executed. # Also note that there is a NOT(in_use_clause) index. - in_use_clause = ( - "(is_current_ubuntu IS TRUE OR is_current_upstream IS TRUE)") + in_use_clause = Or( + IsTrue(TranslationMessage.is_current_ubuntu), + IsTrue(TranslationMessage.is_current_upstream)) # Present a list of language + usage constraints to sql. A language # can either be unconstrained, used, or suggested depending on which # of suggested_languages, used_languages it appears in. @@ -397,26 +404,33 @@ class POTMsgSet(SQLBase): used_languages = used_languages - both_languages lang_used = [] if both_languages: - lang_used.append('TranslationMessage.language IN %s' % - quote(both_languages)) + lang_used.append( + TranslationMessage.languageID.is_in(both_languages)) if used_languages: - lang_used.append('(TranslationMessage.language IN %s AND %s)' % ( - quote(used_languages), in_use_clause)) + lang_used.append(And( + TranslationMessage.languageID.is_in(used_languages), + in_use_clause)) if suggested_languages: - lang_used.append( - '(TranslationMessage.language IN %s AND NOT %s)' % ( - quote(suggested_languages), in_use_clause)) - - msgsets = SQL('''msgsets AS ( - SELECT POTMsgSet.id - FROM POTMsgSet - JOIN TranslationTemplateItem ON - TranslationTemplateItem.potmsgset = POTMsgSet.id - JOIN SuggestivePOTemplate ON - TranslationTemplateItem.potemplate = - SuggestivePOTemplate.potemplate - WHERE POTMsgSet.msgid_singular = %s and POTMsgSet.id <> %s - )''' % sqlvalues(self.msgid_singular, self)) + lang_used.append(And( + TranslationMessage.languageID.is_in(suggested_languages), + Not(in_use_clause))) + + SuggestivePOTemplate = Table('SuggestivePOTemplate') + msgsets = With('msgsets', Select( + POTMsgSet.id, + tables=( + POTMsgSet, + Join( + TranslationTemplateItem, + TranslationTemplateItem.potmsgset == POTMsgSet.id), + Join( + SuggestivePOTemplate, + TranslationTemplateItem.potemplate == + Column('potemplate', SuggestivePOTemplate))), + where=And( + POTMsgSet.msgid_singular == self.msgid_singular, + POTMsgSet.id != self.id))) + msgsets_table = Table('msgsets') # Subquery to find the ids of TranslationMessages that are # matching suggestions. @@ -425,25 +439,23 @@ class POTMsgSet(SQLBase): # excluding older messages that are identical to newer ones in # all translated forms. The Python code can later sort out the # distinct translations per form. - msgstrs = ', '.join([ - 'COALESCE(msgstr%d, -1)' % form - for form in range(TranslationConstants.MAX_PLURAL_FORMS)]) - ids_query_params = { - 'msgstrs': msgstrs, - 'where': '(' + ' OR '.join(lang_used) + ')', - } - ids_query = ''' - SELECT DISTINCT ON (%(msgstrs)s) - TranslationMessage.id - FROM TranslationMessage - JOIN msgsets ON msgsets.id = TranslationMessage.potmsgset - WHERE %(where)s - ORDER BY %(msgstrs)s, date_created DESC - ''' % ids_query_params + msgstrs = [ + Coalesce(getattr(TranslationMessage, 'msgstr%dID' % form), -1) + for form in range(TranslationConstants.MAX_PLURAL_FORMS)] result = IStore(TranslationMessage).with_(msgsets).find( TranslationMessage, - TranslationMessage.id.is_in(SQL(ids_query))) + TranslationMessage.id.is_in(Select( + TranslationMessage.id, + tables=( + TranslationMessage, + Join( + msgsets_table, + TranslationMessage.potmsgset == + Column('id', msgsets_table))), + where=Or(*lang_used), + order_by=(msgstrs + [Desc(TranslationMessage.date_created)]), + distinct=msgstrs))) return shortlist(result, longest_expected=100, hardlimit=2000) @@ -556,17 +568,18 @@ class POTMsgSet(SQLBase): :param prefer_shared: Whether to prefer a shared match over a diverged one. """ - clauses = ['potmsgset = %s' % sqlvalues(self), - 'language = %s' % sqlvalues(pofile.language), - '(potemplate IS NULL OR potemplate = %s)' % sqlvalues( - pofile.potemplate)] + clauses = [ + TranslationMessage.potmsgset == self, + TranslationMessage.language == pofile.language, + Or( + TranslationMessage.potemplate == None, + TranslationMessage.potemplate == pofile.potemplate), + ] for pluralform in range(pofile.plural_forms): - if potranslations[pluralform] is None: - clauses.append('msgstr%s IS NULL' % sqlvalues(pluralform)) - else: - clauses.append('msgstr%s=%s' % ( - sqlvalues(pluralform, potranslations[pluralform]))) + clauses.append( + getattr(TranslationMessage, 'msgstr%d' % pluralform) == + potranslations[pluralform]) remaining_plural_forms = list(range( pofile.plural_forms, TranslationConstants.MAX_PLURAL_FORMS)) @@ -574,18 +587,18 @@ class POTMsgSet(SQLBase): # Prefer either shared or diverged messages, depending on # arguments. if prefer_shared: - order = ['potemplate NULLS FIRST'] + order = [NullsFirst(TranslationMessage.potemplateID)] else: - order = ['potemplate NULLS LAST'] + order = [NullsLast(TranslationMessage.potemplateID)] # Normally at most one message should match. But if there is # more than one, prefer the one that adds the fewest extraneous # plural forms. order.extend([ - 'msgstr%s NULLS FIRST' % quote(form) + NullsFirst(getattr(TranslationMessage, 'msgstr%dID' % form)) for form in remaining_plural_forms]) - matches = list( - TranslationMessage.select(' AND '.join(clauses), orderBy=order)) + matches = list(IStore(TranslationMessage).find( + TranslationMessage, *clauses).order_by(*order)) if len(matches) > 0: if len(matches) > 1: @@ -1214,8 +1227,9 @@ class POTMsgSet(SQLBase): def setSequence(self, potemplate, sequence): """See `IPOTMsgSet`.""" - translation_template_item = TranslationTemplateItem.selectOneBy( - potmsgset=self, potemplate=potemplate) + translation_template_item = IStore(TranslationTemplateItem).find( + TranslationTemplateItem, + potmsgset=self, potemplate=potemplate).one() if translation_template_item is not None: # Update the sequence for the translation template item. translation_template_item.sequence = sequence @@ -1246,8 +1260,9 @@ class POTMsgSet(SQLBase): def getSequence(self, potemplate): """See `IPOTMsgSet`.""" - translation_template_item = TranslationTemplateItem.selectOneBy( - potmsgset=self, potemplate=potemplate) + translation_template_item = IStore(TranslationTemplateItem).find( + TranslationTemplateItem, + potmsgset=self, potemplate=potemplate).one() if translation_template_item is not None: return translation_template_item.sequence else: @@ -1260,5 +1275,5 @@ class POTMsgSet(SQLBase): def getAllTranslationTemplateItems(self): """See `IPOTMsgSet`.""" - return TranslationTemplateItem.selectBy( - potmsgset=self, orderBy=['id']) + return IStore(TranslationTemplateItem).find( + TranslationTemplateItem, potmsgset=self).order_by('id')
_______________________________________________ Mailing list: https://launchpad.net/~launchpad-reviewers Post to : launchpad-reviewers@lists.launchpad.net Unsubscribe : https://launchpad.net/~launchpad-reviewers More help : https://help.launchpad.net/ListHelp