~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).

"""Functions to copy translations from previous to child distroseries."""

__metaclass__ = type

__all__ = [ 'copy_active_translations' ]

from lp.services.database.multitablecopy import MultiTableCopy
from lp.services.database.sqlbase import (
    cursor,
    quote,
    )


def copy_active_translations(child, transaction, logger):
    """Furnish untranslated child `DistroSeries` with previous series's
    translations.

    This method uses `MultiTableCopy` to copy data.

    Translation data for the new series ("child") is first copied into holding
    tables called e.g. "temp_POTemplate_holding_ubuntu_feisty" and processed
    there.  Then, near the end of the procedure, the contents of these holding
    tables are all poured back into the original tables.

    If this procedure fails, it may leave holding tables behind.  This was
    done deliberately to leave some forensics information for failures, and
    also to allow admins to see what data has and has not been copied.

    If a holding table left behind by an abortive run has a column called
    new_id at the end, it contains unfinished data and may as well be dropped.
    If it does not have that column, the holding table was already in the
    process of being poured back into its source table.  In that case the
    sensible thing to do is probably to continue pouring it.
    """
    previous_series = child.previous_series
    if previous_series is None:
        # We don't have a previous series from where we could copy
        # translations.
        return

    translation_tables = [
        'potemplate', 'translationtemplateitem', 'pofile', 'pofiletranslator'
        ]

    full_name = "%s_%s" % (child.distribution.name, child.name)
    copier = MultiTableCopy(full_name, translation_tables, logger=logger)

    # Incremental copy of updates is no longer supported
    assert not child.has_translation_templates, (
           "The child series must not yet have any translation templates.")

    logger.info(
        "Populating blank distroseries %s with translations from %s." %
        (child.name, previous_series.name))

    # Because this function only deals with the case where "child" is a new
    # distroseries without any existing translations attached, it can afford
    # to be much more cavalier with ACID considerations than the function that
    # updates an existing translation based on what's found in the previous
    # series.

    # 1. Extraction phase--for every table involved (called a "source table"
    # in MultiTableCopy parlance), we create a "holding table."  We fill that
    # with all rows from the source table that we want to copy from the
    # previous series.  We make some changes to the copied rows, such as
    # making them belong to ourselves instead of our previous series.
    #
    # The first phase does not modify any tables that other clients may want
    # to use, avoiding locking problems.
    #
    # 2. Pouring phase.  From each holding table we pour all rows back into
    # the matching source table, deleting them from the holding table as we
    # go.  The holding table is dropped once empty.
    #
    # The second phase is "batched," moving only a small number of rows at a
    # time, then performing an intermediate commit.  This avoids holding too
    # many locks for too long and disrupting regular database service.

    # Clean up any remains from a previous run.  If we got here, that means
    # that any such remains are unsalvagable.
    copier.dropHoldingTables()

    # Copy relevant POTemplates from existing series into a holding table,
    # complete with their original id fields.
    where = 'distroseries = %s AND iscurrent' % quote(previous_series)
    copier.extract('potemplate', [], where)

    # Now that we have the data "in private," where nobody else can see it,
    # we're free to play with it.  No risk of locking other processes out of
    # the database.
    # Change series identifiers in the holding table to point to the child
    # (right now they all bear the previous series's id) and set creation
    # dates to the current transaction time.
    cursor().execute('''
        UPDATE %s
        SET
            distroseries = %s,
            datecreated =
                timezone('UTC'::text,
                    ('now'::text)::timestamp(6) with time zone)
    ''' % (copier.getHoldingTableName('potemplate'), quote(child)))


    # Copy each TranslationTemplateItem whose template we copied, and let
    # MultiTableCopy replace each potemplate reference with a reference to
    # our copy of the original POTMsgSet's potemplate.
    copier.extract('translationtemplateitem', ['potemplate'], 'sequence > 0')

    # Copy POFiles, making them refer to the child's copied POTemplates.
    copier.extract('pofile', ['potemplate'])

    # Copy POFileTranslators, making them refer to the child's copied POFile.
    copier.extract('pofiletranslator', ['pofile'])

    # Finally, pour the holding tables back into the originals.
    copier.pour(transaction)