Merge lp:~wallyworld/launchpad/ppa-packages-timeout-1071581 into lp:launchpad

Proposed by Ian Booth on 2012-11-02
Status: Merged
Approved by: Curtis Hovey on 2012-11-05
Approved revision: no longer in the source branch.
Merged at revision: 16244
Proposed branch: lp:~wallyworld/launchpad/ppa-packages-timeout-1071581
Merge into: lp:launchpad
Diff against target: 857 lines (+534/-57)
11 files modified
database/sampledata/current-dev.sql (+47/-0)
database/sampledata/current.sql (+43/-0)
database/schema/security.cfg (+3/-0)
lib/lp/registry/browser/person.py (+10/-8)
lib/lp/registry/doc/person.txt (+1/-1)
lib/lp/registry/model/person.py (+40/-48)
lib/lp/scripts/garbo.py (+194/-0)
lib/lp/scripts/tests/test_garbo.py (+111/-0)
lib/lp/soyuz/configure.zcml (+6/-0)
lib/lp/soyuz/interfaces/reporting.py (+30/-0)
lib/lp/soyuz/model/reporting.py (+49/-0)
To merge this branch: bzr merge lp:~wallyworld/launchpad/ppa-packages-timeout-1071581
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code 2012-11-02 Approve on 2012-11-05
Review via email: mp+132646@code.launchpad.net

This proposal supersedes a proposal from 2012-10-31.

Commit Message

Introduce a new reporting table (populated by a frequent garbo job) to allow efficient loading of source package releases related to a person.

Description of the Change

== Pre Implementation ==

Discussed with wgrant.

== Implementation ==

So Postgres cannot efficiently execute the required DISTINCT ON query needed to only find the latest published packages.
After a previous attempt to solve this, the best approach is to introduce a new denormalised reporting table and populate the table with a garbo job. The data does not have to be super up-to-date, so a frequently run garbo job will suffice.

This mp contains a few parts:

1. New model class LatestPersonSourcepackageReleaseCache (rows from the denormalised reporting table)

2. Tweaks to the view code and person model to get data from the new denormed table. A huge hunk of slow SQL is replaced by a simple select.

3. Garbo job to populate the new reporting table

4. New sample data for the existing model data

The denormalised table contains data rows keyed on both package creators and maintainers. This allows the views to filter on either of these as required.

The garbo job maintains state between runs in a new generic GarboJobState table. This replaces the use of memcache which is now deprecated. The job first does the creators and then the maintainers.

This mp requires the corresponding db-devel branch to land in order for the tables to be created.

== Tests ==

Add a new test for the new garbo job.

== Lint ==

Checking for conflicts and issues in changed files.

Linting changed files:
  database/schema/security.cfg
  lib/lp/registry/browser/person.py
  lib/lp/registry/model/person.py
  lib/lp/scripts/garbo.py
  lib/lp/soyuz/configure.zcml
  lib/lp/soyuz/interfaces/reporting.py
  lib/lp/soyuz/model/reporting.py

To post a comment you must log in.
Curtis Hovey (sinzui) wrote : Posted in a previous version of this proposal

Line 123 looks inconsistent because the storm column does not use ID
    SourcePackagePublishingHistory.sourcepackagereleaseID

The while-loop looks odd. I don't like use the done variable, but I see you wanted something separate from max_results. I don't understand what happens when max_results is None or 0 -- they seem to lead to contradictory behaviour. I suppose 0 is really a value error for max_results. What happens when max_results is None and the the last batch is returns less than the batch size? eg. rs has only 15 more items, it iterates over them, then back to the start of the loop to get the next batch using a slice that exceeds everything in the rs.

review: Needs Information (code)
Ian Booth (wallyworld) wrote : Posted in a previous version of this proposal

I am going to scrap this work as is - it turns out there will still be
performance issues for some users, those who will have many 1000s of
records to be displayed. I was of the assumption that the number of
records would normally be much smaller than that.

So the plan now is to introduce a new denormalised table, designed for
reporting, and maintain it with a frequent garbo job, since the data
does not have to be immediately current.

On Wed 31 Oct 2012 23:24:23 EST, Curtis Hovey wrote:
> Review: Needs Information code
>
> Line 123 looks inconsistent because the storm column does not use ID
> SourcePackagePublishingHistory.sourcepackagereleaseID
>
> The while-loop looks odd. I don't like use the done variable, but I see you wanted something separate from max_results. I don't understand what happens when max_results is None or 0 -- they seem to lead to contradictory behaviour. I suppose 0 is really a value error for max_results. What happens when max_results is None and the the last batch is returns less than the batch size? eg. rs has only 15 more items, it iterates over them, then back to the start of the loop to get the next batch using a slice that exceeds everything in the rs.

Curtis Hovey (sinzui) wrote :

Thank you.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/sampledata/current-dev.sql'
2--- database/sampledata/current-dev.sql 2012-10-24 23:57:38 +0000
3+++ database/sampledata/current-dev.sql 2012-11-06 08:03:49 +0000
4@@ -4348,6 +4348,13 @@
5
6
7
8+ALTER TABLE garbojobstate DISABLE TRIGGER ALL;
9+
10+
11+
12+ALTER TABLE garbojobstate ENABLE TRIGGER ALL;
13+
14+
15 ALTER TABLE hwvendorname DISABLE TRIGGER ALL;
16
17 INSERT INTO hwvendorname (id, name) VALUES (1, 'MSI');
18@@ -4829,6 +4836,46 @@
19 ALTER TABLE languagepack ENABLE TRIGGER ALL;
20
21
22+ALTER TABLE latestpersonsourcepackagereleasecache DISABLE TRIGGER ALL;
23+
24+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (1, 1, '2004-09-27 11:57:13', 1, NULL, 1, 1, 1, 1, 14);
25+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (2, 5, '2005-03-10 16:30:00', 1, NULL, 1, 1, 3, 10, 16);
26+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (3, 4, '2005-03-14 18:00:00', 1, NULL, 1, 1, 1, 10, 17);
27+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (4, 9, '2005-06-24 20:59:31.439579', 1, NULL, 1, 1, 3, 14, 21);
28+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (5, 10, '2005-02-03 08:50:00', 1, NULL, 1, 1, 1, 19, 23);
29+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (6, 15, '2005-07-01 22:47:00', 1, NULL, 1, 1, 8, 19, 24);
30+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (7, 12, '2005-08-01 14:10:00', 1, NULL, 1, 1, 3, 19, 25);
31+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (8, 13, '2005-12-22 18:19:00', 1, NULL, 1, 1, 3, 20, 26);
32+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (9, 16, '2006-02-10 11:19:00', 1, NULL, 1, 1, 3, 21, 27);
33+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (10, 19, '2005-12-22 18:19:00', 1, NULL, 1, 1, 3, 22, 28);
34+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (11, 22, '2006-12-01 13:19:01', 1, NULL, 1, 1, 10, 23, 32);
35+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (12, 23, '2006-12-01 13:19:01', 1, NULL, 1, 1, 10, 24, 33);
36+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (13, 31, '2006-04-11 11:19:01', 1, NULL, 2, 10, 1, 26, 35);
37+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (14, 30, '2007-08-09 21:25:37.832976', 243606, NULL, 4, 12, 10, 27, 36);
38+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (15, 33, '2005-08-01 14:10:00', 1, NULL, 1, 1, 3, 9, 39);
39+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (16, 35, '2006-07-05 14:10:00', 1, NULL, 1, 1, 3, 1, 41);
40+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (17, 1, '2004-09-27 11:57:13', NULL, 1, 1, 1, 1, 1, 14);
41+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (18, 2, '2004-09-27 11:57:13', NULL, 1, 1, 1, 3, 9, 15);
42+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (19, 5, '2005-03-10 16:30:00', NULL, 1, 1, 1, 3, 10, 16);
43+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (20, 4, '2005-03-14 18:00:00', NULL, 1, 1, 1, 1, 10, 17);
44+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (21, 9, '2005-06-24 20:59:31.439579', NULL, 1, 1, 1, 3, 14, 21);
45+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (22, 10, '2005-02-03 08:50:00', NULL, 1, 1, 1, 1, 19, 23);
46+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (23, 14, '2005-07-01 22:47:00', NULL, 1, 1, 1, 8, 19, 24);
47+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (24, 20, '2005-08-01 14:10:00', NULL, 16, 1, 1, 3, 19, 25);
48+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (25, 13, '2005-12-22 18:19:00', NULL, 16, 1, 1, 3, 20, 26);
49+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (26, 16, '2006-02-10 11:19:00', NULL, 16, 1, 1, 3, 21, 27);
50+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (27, 17, '2005-12-22 18:19:00', NULL, 16, 1, 1, 3, 22, 28);
51+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (28, 22, '2006-12-01 13:19:01', NULL, 16, 1, 1, 10, 23, 32);
52+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (29, 25, '2006-12-01 13:19:01', NULL, 16, 1, 1, 10, 24, 33);
53+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (30, 31, '2006-04-11 11:19:01', NULL, 16, 2, 10, 1, 26, 35);
54+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (31, 30, '2007-08-09 21:25:37.832976', NULL, 243606, 4, 12, 10, 27, 36);
55+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (32, 33, '2005-08-01 14:10:00', NULL, 16, 1, 1, 3, 9, 39);
56+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (33, 35, '2006-07-05 14:10:00', NULL, 16, 1, 1, 3, 1, 41);
57+
58+
59+ALTER TABLE latestpersonsourcepackagereleasecache ENABLE TRIGGER ALL;
60+
61+
62 ALTER TABLE launchpaddatabaserevision DISABLE TRIGGER ALL;
63
64
65
66=== modified file 'database/sampledata/current.sql'
67--- database/sampledata/current.sql 2012-10-24 23:57:38 +0000
68+++ database/sampledata/current.sql 2012-11-06 08:03:49 +0000
69@@ -4275,6 +4275,13 @@
70
71
72
73+ALTER TABLE garbojobstate DISABLE TRIGGER ALL;
74+
75+
76+
77+ALTER TABLE garbojobstate ENABLE TRIGGER ALL;
78+
79+
80 ALTER TABLE hwvendorname DISABLE TRIGGER ALL;
81
82 INSERT INTO hwvendorname (id, name) VALUES (1, 'MSI');
83@@ -4756,6 +4763,42 @@
84 ALTER TABLE languagepack ENABLE TRIGGER ALL;
85
86
87+ALTER TABLE latestpersonsourcepackagereleasecache DISABLE TRIGGER ALL;
88+
89+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (1, 1, '2004-09-27 11:57:13', 1, NULL, 1, 1, 1, 1, 14);
90+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (2, 5, '2005-03-10 16:30:00', 1, NULL, 1, 1, 3, 10, 16);
91+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (3, 4, '2005-03-14 18:00:00', 1, NULL, 1, 1, 1, 10, 17);
92+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (4, 9, '2005-06-24 20:59:31.439579', 1, NULL, 1, 1, 3, 14, 21);
93+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (5, 10, '2005-02-03 08:50:00', 1, NULL, 1, 1, 1, 19, 23);
94+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (6, 15, '2005-07-01 22:47:00', 1, NULL, 1, 1, 8, 19, 24);
95+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (7, 12, '2005-08-01 14:10:00', 1, NULL, 1, 1, 3, 19, 25);
96+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (8, 13, '2005-12-22 18:19:00', 1, NULL, 1, 1, 3, 20, 26);
97+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (9, 16, '2006-02-10 11:19:00', 1, NULL, 1, 1, 3, 21, 27);
98+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (10, 19, '2005-12-22 18:19:00', 1, NULL, 1, 1, 3, 22, 28);
99+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (11, 22, '2006-12-01 13:19:01', 1, NULL, 1, 1, 10, 23, 32);
100+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (12, 23, '2006-12-01 13:19:01', 1, NULL, 1, 1, 10, 24, 33);
101+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (13, 31, '2006-04-11 11:19:01', 1, NULL, 2, 10, 1, 26, 35);
102+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (14, 30, '2007-08-09 21:25:37.832976', 243606, NULL, 4, 12, 10, 27, 36);
103+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (17, 1, '2004-09-27 11:57:13', NULL, 1, 1, 1, 1, 1, 14);
104+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (18, 2, '2004-09-27 11:57:13', NULL, 1, 1, 1, 3, 9, 15);
105+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (19, 5, '2005-03-10 16:30:00', NULL, 1, 1, 1, 3, 10, 16);
106+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (20, 4, '2005-03-14 18:00:00', NULL, 1, 1, 1, 1, 10, 17);
107+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (21, 9, '2005-06-24 20:59:31.439579', NULL, 1, 1, 1, 3, 14, 21);
108+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (22, 10, '2005-02-03 08:50:00', NULL, 1, 1, 1, 1, 19, 23);
109+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (23, 14, '2005-07-01 22:47:00', NULL, 1, 1, 1, 8, 19, 24);
110+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (24, 20, '2005-08-01 14:10:00', NULL, 16, 1, 1, 3, 19, 25);
111+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (25, 13, '2005-12-22 18:19:00', NULL, 16, 1, 1, 3, 20, 26);
112+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (26, 16, '2006-02-10 11:19:00', NULL, 16, 1, 1, 3, 21, 27);
113+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (27, 17, '2005-12-22 18:19:00', NULL, 16, 1, 1, 3, 22, 28);
114+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (28, 22, '2006-12-01 13:19:01', NULL, 16, 1, 1, 10, 23, 32);
115+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (29, 25, '2006-12-01 13:19:01', NULL, 16, 1, 1, 10, 24, 33);
116+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (30, 31, '2006-04-11 11:19:01', NULL, 16, 2, 10, 1, 26, 35);
117+INSERT INTO latestpersonsourcepackagereleasecache (id, publication, date_uploaded, creator, maintainer, archive_purpose, upload_archive, upload_distroseries, sourcepackagename, sourcepackagerelease) VALUES (31, 30, '2007-08-09 21:25:37.832976', NULL, 243606, 4, 12, 10, 27, 36);
118+
119+
120+ALTER TABLE latestpersonsourcepackagereleasecache ENABLE TRIGGER ALL;
121+
122+
123 ALTER TABLE launchpaddatabaserevision DISABLE TRIGGER ALL;
124
125
126
127=== modified file 'database/schema/security.cfg'
128--- database/schema/security.cfg 2012-11-01 19:26:37 +0000
129+++ database/schema/security.cfg 2012-11-06 08:03:49 +0000
130@@ -217,6 +217,7 @@
131 public.karmatotalcache = SELECT, DELETE, UPDATE
132 public.language = SELECT
133 public.languagepack = SELECT, INSERT, UPDATE
134+public.latestpersonsourcepackagereleasecache = SELECT
135 public.launchpadstatistic = SELECT
136 public.libraryfilealias = SELECT, INSERT, UPDATE, DELETE
137 public.libraryfiledownloadcount = SELECT, INSERT, UPDATE
138@@ -2239,8 +2240,10 @@
139 public.codeimportresult = SELECT, DELETE
140 public.commercialsubscription = SELECT, UPDATE
141 public.emailaddress = SELECT, UPDATE, DELETE
142+public.garbojobstate = SELECT, INSERT, UPDATE, DELETE
143 public.hwsubmission = SELECT, UPDATE
144 public.job = SELECT, INSERT, DELETE
145+public.latestpersonsourcepackagereleasecache = SELECT, INSERT, UPDATE
146 public.logintoken = SELECT, DELETE
147 public.mailinglistsubscription = SELECT, DELETE
148 public.milestonetag = SELECT
149
150=== modified file 'lib/lp/registry/browser/person.py'
151--- lib/lp/registry/browser/person.py 2012-11-01 03:41:36 +0000
152+++ lib/lp/registry/browser/person.py 2012-11-06 08:03:49 +0000
153@@ -3644,17 +3644,19 @@
154 builds_by_package = {}
155 needs_build_by_package = {}
156 for package in package_releases:
157- builds_by_package[package] = []
158- needs_build_by_package[package] = False
159+ builds_by_package[package.id] = []
160+ needs_build_by_package[package.id] = False
161 for build in all_builds:
162 if build.status == BuildStatus.FAILEDTOBUILD:
163- builds_by_package[build.source_package_release].append(build)
164+ builds_by_package[
165+ build.source_package_release.id].append(build)
166 needs_build = build.status in [
167 BuildStatus.NEEDSBUILD,
168 BuildStatus.MANUALDEPWAIT,
169 BuildStatus.CHROOTWAIT,
170 ]
171- needs_build_by_package[build.source_package_release] = needs_build
172+ needs_build_by_package[
173+ build.source_package_release.id] = needs_build
174
175 return (builds_by_package, needs_build_by_package)
176
177@@ -3665,8 +3667,8 @@
178
179 return [
180 SourcePackageReleaseWithStats(
181- package, builds_by_package[package],
182- needs_build_by_package[package])
183+ package, builds_by_package[package.id],
184+ needs_build_by_package[package.id])
185 for package in package_releases]
186
187 def _addStatsToPublishings(self, publishings):
188@@ -3679,8 +3681,8 @@
189
190 return [
191 SourcePackagePublishingHistoryWithStats(
192- spph, builds_by_package[spph.sourcepackagerelease],
193- needs_build_by_package[spph.sourcepackagerelease])
194+ spph, builds_by_package[spph.sourcepackagerelease.id],
195+ needs_build_by_package[spph.sourcepackagerelease.id])
196 for spph in filtered_spphs]
197
198 def setUpBatch(self, packages):
199
200=== modified file 'lib/lp/registry/doc/person.txt'
201--- lib/lp/registry/doc/person.txt 2012-10-26 13:11:30 +0000
202+++ lib/lp/registry/doc/person.txt 2012-11-06 08:03:49 +0000
203@@ -1004,7 +1004,7 @@
204 issue mentioned in bug 157303, where source with same creator and
205 maintainer got omitted from the results:
206
207- >>> any_spr = mark_spreleases[0]
208+ >>> any_spr = mark_spreleases[0].sourcepackagerelease
209 >>> naked_spr = removeSecurityProxy(any_spr)
210 >>> naked_spr.maintainer = mark
211 >>> flush_database_updates()
212
213=== modified file 'lib/lp/registry/model/person.py'
214--- lib/lp/registry/model/person.py 2012-11-03 18:05:44 +0000
215+++ lib/lp/registry/model/person.py 2012-11-06 08:03:49 +0000
216@@ -327,6 +327,7 @@
217 Archive,
218 validate_ppa,
219 )
220+from lp.soyuz.model.reporting import LatestPersonSourcepackageReleaseCache
221 from lp.soyuz.model.publishing import SourcePackagePublishingHistory
222 from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
223 from lp.translations.model.hastranslationimports import (
224@@ -2810,8 +2811,8 @@
225 return self._latestReleasesQuery(uploader_only=True, ppa_only=True)
226
227 def _releasesQueryFilter(self, uploader_only=False, ppa_only=False):
228- """Return the filter used to find sourcepackagereleases (SPRs)
229- related to this person.
230+ """Return the filter used to find latest published source package
231+ releases (SPRs) related to this person.
232
233 :param uploader_only: controls if we are interested in SPRs where
234 the person in question is only the uploader (creator) and not the
235@@ -2827,24 +2828,38 @@
236 'uploader_only' because there shouldn't be any sense of maintainership
237 for packages uploaded to PPAs by someone else than the user himself.
238 """
239- clauses = [SourcePackageRelease.upload_archive == Archive.id]
240-
241+ clauses = []
242 if uploader_only:
243- clauses.append(SourcePackageRelease.creator == self)
244-
245+ clauses.append(
246+ LatestPersonSourcepackageReleaseCache.creator_id == self.id)
247 if ppa_only:
248 # Source maintainer is irrelevant for PPA uploads.
249 pass
250 elif uploader_only:
251- clauses.append(SourcePackageRelease.maintainer != self)
252+ lpspr = ClassAlias(LatestPersonSourcepackageReleaseCache, 'lpspr')
253+ clauses.append(Not(Exists(Select(1,
254+ where=And(
255+ lpspr.sourcepackagename_id ==
256+ LatestPersonSourcepackageReleaseCache.sourcepackagename_id,
257+ lpspr.upload_archive_id ==
258+ LatestPersonSourcepackageReleaseCache.upload_archive_id,
259+ lpspr.upload_distroseries_id ==
260+ LatestPersonSourcepackageReleaseCache.upload_distroseries_id,
261+ lpspr.archive_purpose != ArchivePurpose.PPA,
262+ lpspr.maintainer_id ==
263+ LatestPersonSourcepackageReleaseCache.creator_id),
264+ tables=lpspr))))
265 else:
266- clauses.append(SourcePackageRelease.maintainer == self)
267-
268+ clauses.append(
269+ LatestPersonSourcepackageReleaseCache.maintainer_id == self.id)
270 if ppa_only:
271- clauses.append(Archive.purpose == ArchivePurpose.PPA)
272+ clauses.append(
273+ LatestPersonSourcepackageReleaseCache.archive_purpose ==
274+ ArchivePurpose.PPA)
275 else:
276- clauses.append(Archive.purpose != ArchivePurpose.PPA)
277-
278+ clauses.append(
279+ LatestPersonSourcepackageReleaseCache.archive_purpose !=
280+ ArchivePurpose.PPA)
281 return clauses
282
283 def _hasReleasesQuery(self, uploader_only=False, ppa_only=False):
284@@ -2852,50 +2867,27 @@
285 See `_releasesQueryFilter` for details on the criteria used.
286 """
287 clauses = self._releasesQueryFilter(uploader_only, ppa_only)
288- spph = ClassAlias(SourcePackagePublishingHistory, "spph")
289- tables = (
290- SourcePackageRelease,
291- Join(
292- spph, spph.sourcepackagereleaseID == SourcePackageRelease.id),
293- Join(Archive, Archive.id == spph.archiveID))
294- rs = Store.of(self).using(*tables).find(
295- SourcePackageRelease.id, clauses)
296+ rs = Store.of(self).using(LatestPersonSourcepackageReleaseCache).find(
297+ LatestPersonSourcepackageReleaseCache.publication_id, clauses)
298 return not rs.is_empty()
299
300 def _latestReleasesQuery(self, uploader_only=False, ppa_only=False):
301- """Return the sourcepackagereleases (SPRs) related to this person.
302+ """Return the sourcepackagereleases records related to this person.
303 See `_releasesQueryFilter` for details on the criteria used."""
304 clauses = self._releasesQueryFilter(uploader_only, ppa_only)
305- spph = ClassAlias(SourcePackagePublishingHistory, "spph")
306 rs = Store.of(self).find(
307- SourcePackageRelease,
308- SourcePackageRelease.id.is_in(
309- Select(
310- SourcePackageRelease.id,
311- tables=[
312- SourcePackageRelease,
313- Join(
314- spph,
315- spph.sourcepackagereleaseID ==
316- SourcePackageRelease.id),
317- Join(Archive, Archive.id == spph.archiveID)],
318- where=And(*clauses),
319- order_by=[SourcePackageRelease.upload_distroseriesID,
320- SourcePackageRelease.sourcepackagenameID,
321- SourcePackageRelease.upload_archiveID,
322- Desc(SourcePackageRelease.dateuploaded)],
323- distinct=(
324- SourcePackageRelease.upload_distroseriesID,
325- SourcePackageRelease.sourcepackagenameID,
326- SourcePackageRelease.upload_archiveID)))
327- ).order_by(
328- Desc(SourcePackageRelease.dateuploaded), SourcePackageRelease.id)
329+ LatestPersonSourcepackageReleaseCache, *clauses).order_by(
330+ Desc(LatestPersonSourcepackageReleaseCache.dateuploaded))
331
332 def load_related_objects(rows):
333- list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
334- set(map(attrgetter("maintainerID"), rows))))
335- bulk.load_related(SourcePackageName, rows, ['sourcepackagenameID'])
336- bulk.load_related(Archive, rows, ['upload_archiveID'])
337+ if rows and rows[0].maintainer_id:
338+ list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
339+ set(map(attrgetter("maintainer_id"), rows))))
340+ bulk.load_related(
341+ SourcePackageName, rows, ['sourcepackagename_id'])
342+ bulk.load_related(
343+ SourcePackageRelease, rows, ['sourcepackagerelease_id'])
344+ bulk.load_related(Archive, rows, ['upload_archive_id'])
345
346 return DecoratedResultSet(rs, pre_iter_hook=load_related_objects)
347
348
349=== modified file 'lib/lp/scripts/garbo.py'
350--- lib/lp/scripts/garbo.py 2012-10-22 02:30:44 +0000
351+++ lib/lp/scripts/garbo.py 2012-11-06 08:03:49 +0000
352@@ -8,6 +8,8 @@
353 'DailyDatabaseGarbageCollector',
354 'FrequentDatabaseGarbageCollector',
355 'HourlyDatabaseGarbageCollector',
356+ 'load_garbo_job_state',
357+ 'save_garbo_job_state',
358 ]
359
360 from datetime import (
361@@ -17,6 +19,7 @@
362 import logging
363 import multiprocessing
364 import os
365+import simplejson
366 import threading
367 import time
368
369@@ -28,11 +31,17 @@
370 from psycopg2 import IntegrityError
371 import pytz
372 from storm.expr import (
373+ Alias,
374+ And,
375+ Desc,
376 In,
377+ Insert,
378+ Join,
379 Like,
380 Select,
381 Update,
382 )
383+from storm.info import ClassAlias
384 from storm.locals import (
385 Max,
386 Min,
387@@ -105,6 +114,10 @@
388 )
389 from lp.services.session.model import SessionData
390 from lp.services.verification.model.logintoken import LoginToken
391+from lp.soyuz.model.archive import Archive
392+from lp.soyuz.model.publishing import SourcePackagePublishingHistory
393+from lp.soyuz.model.reporting import LatestPersonSourcepackageReleaseCache
394+from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
395 from lp.translations.interfaces.potemplate import IPOTemplateSet
396 from lp.translations.model.potmsgset import POTMsgSet
397 from lp.translations.model.potranslation import POTranslation
398@@ -120,6 +133,33 @@
399 ONE_DAY_IN_SECONDS = 24 * 60 * 60
400
401
402+# Garbo jobs may choose to persist state between invocations, if it is likely
403+# that not all data can be processed in a single run. These utility methods
404+# provide convenient access to that state data.
405+def load_garbo_job_state(job_name):
406+ # Load the json state data for the given job name.
407+ store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
408+ job_data = store.execute(
409+ "SELECT json_data FROM GarboJobState WHERE name = ?",
410+ params=(unicode(job_name),)).get_one()
411+ if job_data:
412+ return simplejson.loads(job_data[0])
413+ return None
414+
415+
416+def save_garbo_job_state(job_name, job_data):
417+ # Save the json state data for the given job name.
418+ store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
419+ json_data = simplejson.dumps(job_data, ensure_ascii=False)
420+ result = store.execute(
421+ "UPDATE GarboJobState SET json_data = ? WHERE name = ?",
422+ params=(json_data, unicode(job_name)))
423+ if result.rowcount == 0:
424+ store.execute(
425+ "INSERT INTO GarboJobState(name, json_data) "
426+ "VALUES (?, ?)", params=(unicode(job_name), unicode(json_data)))
427+
428+
429 class BulkPruner(TunableLoop):
430 """A abstract ITunableLoop base class for simple pruners.
431
432@@ -423,6 +463,159 @@
433 transaction.commit()
434
435
436+class PopulateLatestPersonSourcepackageReleaseCache(TunableLoop):
437+ """Populate the LatestPersonSourcepackageReleaseCache table.
438+
439+ The LatestPersonSourcepackageReleaseCache contains 2 sets of data, one set
440+ for package maintainers and another for package creators. This job first
441+ populates the creator data and then does the maintainer data.
442+ """
443+ maximum_chunk_size = 1000
444+
445+ def __init__(self, log, abort_time=None):
446+ super_cl = super(PopulateLatestPersonSourcepackageReleaseCache, self)
447+ super_cl.__init__(log, abort_time)
448+ self.store = getUtility(IStoreSelector).get(MAIN_STORE, MASTER_FLAVOR)
449+ # Keep a record of the processed source package release id and data
450+ # type (creator or maintainer) so we know where to job got up to.
451+ self.next_id = 0
452+ self.current_person_filter_type = 'creator'
453+ self.starting_person_filter_type = self.current_person_filter_type
454+ self.job_name = self.__class__.__name__
455+ job_data = load_garbo_job_state(self.job_name)
456+ if job_data:
457+ self.next_id = job_data['next_id']
458+ self.current_person_filter_type = job_data['person_filter_type']
459+ self.starting_person_filter_type = self.current_person_filter_type
460+
461+ def getPendingUpdates(self):
462+ # Load the latest published source package release data keyed on either
463+ # creator or maintainer as required.
464+ if self.current_person_filter_type == 'creator':
465+ person_filter = SourcePackageRelease.creatorID
466+ else:
467+ person_filter = SourcePackageRelease.maintainerID
468+ spph = ClassAlias(SourcePackagePublishingHistory, "spph")
469+ origin = [
470+ SourcePackageRelease,
471+ Join(
472+ spph,
473+ And(spph.sourcepackagereleaseID == SourcePackageRelease.id,
474+ spph.archiveID == SourcePackageRelease.upload_archiveID))]
475+ spr_select = self.store.using(*origin).find(
476+ (SourcePackageRelease.id, Alias(spph.id, 'spph_id')),
477+ SourcePackageRelease.id > self.next_id
478+ ).order_by(
479+ person_filter,
480+ SourcePackageRelease.upload_distroseriesID,
481+ SourcePackageRelease.sourcepackagenameID,
482+ SourcePackageRelease.upload_archiveID,
483+ Desc(SourcePackageRelease.dateuploaded),
484+ SourcePackageRelease.id
485+ ).config(distinct=(
486+ person_filter,
487+ SourcePackageRelease.upload_distroseriesID,
488+ SourcePackageRelease.sourcepackagenameID,
489+ SourcePackageRelease.upload_archiveID))._get_select()
490+
491+ spr = Alias(spr_select, 'spr')
492+ origin = [
493+ SourcePackageRelease,
494+ Join(spr, SQL('spr.id') == SourcePackageRelease.id),
495+ Join(Archive, Archive.id == SourcePackageRelease.upload_archiveID)]
496+ rs = self.store.using(*origin).find(
497+ (SourcePackageRelease.id,
498+ person_filter,
499+ SourcePackageRelease.upload_archiveID,
500+ Archive.purpose,
501+ SourcePackageRelease.upload_distroseriesID,
502+ SourcePackageRelease.sourcepackagenameID,
503+ SourcePackageRelease.dateuploaded, SQL('spph_id'))
504+ ).order_by(SourcePackageRelease.id)
505+ return rs
506+
507+ def isDone(self):
508+ # If there is no more data to process for creators, switch over to
509+ # processing data for maintainers, or visa versa.
510+ current_count = self.getPendingUpdates().count()
511+ if current_count == 0:
512+ if (self.current_person_filter_type !=
513+ self.starting_person_filter_type):
514+ return True
515+ if self.current_person_filter_type == 'creator':
516+ self.current_person_filter_type = 'maintainer'
517+ else:
518+ self.current_person_filter_type = 'creator'
519+ self.next_id = 0
520+ current_count = self.getPendingUpdates().count()
521+ return current_count == 0
522+
523+ def update_cache(self, updates):
524+ # Update the LatestPersonSourcepackageReleaseCache table. Records for
525+ # each creator/maintainer will either be new inserts or updates. We try
526+ # to update first, and gather data for missing (new) records along the
527+ # way. At the end, a bulk insert is done for any new data.
528+ # Updates is a list of data records (tuples of values).
529+ # Each record is keyed on:
530+ # - (creator/maintainer), archive, distroseries, sourcepackagename
531+ inserts = []
532+ columns = (
533+ LatestPersonSourcepackageReleaseCache.sourcepackagerelease_id,
534+ LatestPersonSourcepackageReleaseCache.creator_id,
535+ LatestPersonSourcepackageReleaseCache.maintainer_id,
536+ LatestPersonSourcepackageReleaseCache.upload_archive_id,
537+ LatestPersonSourcepackageReleaseCache.archive_purpose,
538+ LatestPersonSourcepackageReleaseCache.upload_distroseries_id,
539+ LatestPersonSourcepackageReleaseCache.sourcepackagename_id,
540+ LatestPersonSourcepackageReleaseCache.dateuploaded,
541+ LatestPersonSourcepackageReleaseCache.publication_id,
542+ )
543+ for update in updates:
544+ (spr_id, person_id, archive_id, purpose,
545+ distroseries_id, spn_id, dateuploaded, spph_id) = update
546+ if self.current_person_filter_type == 'creator':
547+ creator_id = person_id
548+ maintainer_id = None
549+ else:
550+ creator_id = None
551+ maintainer_id = person_id
552+ values = (
553+ spr_id, creator_id, maintainer_id, archive_id, purpose.value,
554+ distroseries_id, spn_id, dateuploaded, spph_id)
555+ data = dict(zip(columns, values))
556+ result = self.store.execute(Update(
557+ data, And(
558+ LatestPersonSourcepackageReleaseCache.upload_archive_id ==
559+ archive_id,
560+ LatestPersonSourcepackageReleaseCache.upload_distroseries_id ==
561+ distroseries_id,
562+ LatestPersonSourcepackageReleaseCache.sourcepackagename_id ==
563+ spn_id,
564+ LatestPersonSourcepackageReleaseCache.creator_id ==
565+ creator_id,
566+ LatestPersonSourcepackageReleaseCache.maintainer_id ==
567+ maintainer_id)))
568+ if result.rowcount == 0:
569+ inserts.append(values)
570+ if inserts:
571+ self.store.execute(Insert(columns, values=inserts))
572+
573+ def __call__(self, chunk_size):
574+ max_id = self.next_id
575+ updates = []
576+ for update in (self.getPendingUpdates()[:chunk_size]):
577+ updates.append(update)
578+ max_id = update[0]
579+ self.update_cache(updates)
580+
581+ self.next_id = max_id
582+ self.store.flush()
583+ save_garbo_job_state(self.job_name, {
584+ 'next_id': max_id,
585+ 'person_filter_type': self.current_person_filter_type})
586+ transaction.commit()
587+
588+
589 class OpenIDConsumerNoncePruner(TunableLoop):
590 """An ITunableLoop to prune old OpenIDConsumerNonce records.
591
592@@ -1339,6 +1532,7 @@
593 OpenIDConsumerAssociationPruner,
594 AntiqueSessionPruner,
595 VoucherRedeemer,
596+ PopulateLatestPersonSourcepackageReleaseCache,
597 ]
598 experimental_tunable_loops = []
599
600
601=== modified file 'lib/lp/scripts/tests/test_garbo.py'
602--- lib/lp/scripts/tests/test_garbo.py 2012-10-22 02:30:44 +0000
603+++ lib/lp/scripts/tests/test_garbo.py 2012-11-06 08:03:49 +0000
604@@ -11,6 +11,7 @@
605 timedelta,
606 )
607 import logging
608+import pytz
609 from StringIO import StringIO
610 import time
611
612@@ -69,8 +70,10 @@
613 DuplicateSessionPruner,
614 FrequentDatabaseGarbageCollector,
615 HourlyDatabaseGarbageCollector,
616+ load_garbo_job_state,
617 LoginTokenPruner,
618 OpenIDConsumerAssociationPruner,
619+ save_garbo_job_state,
620 UnusedSessionPruner,
621 )
622 from lp.services.config import config
623@@ -109,6 +112,8 @@
624 from lp.services.verification.interfaces.authtoken import LoginTokenType
625 from lp.services.verification.model.logintoken import LoginToken
626 from lp.services.worlddata.interfaces.language import ILanguageSet
627+from lp.soyuz.enums import PackagePublishingStatus
628+from lp.soyuz.model.reporting import LatestPersonSourcepackageReleaseCache
629 from lp.testing import (
630 FakeAdapterMixin,
631 person_logged_in,
632@@ -423,6 +428,15 @@
633 collector.main()
634 return collector
635
636+ def test_persist_garbo_state(self):
637+ # Test that loading and saving garbo job state works.
638+ save_garbo_job_state('job', {'data': 1})
639+ data = load_garbo_job_state('job')
640+ self.assertEqual({'data': 1}, data)
641+ save_garbo_job_state('job', {'data': 2})
642+ data = load_garbo_job_state('job')
643+ self.assertEqual({'data': 2}, data)
644+
645 def test_OAuthNoncePruner(self):
646 now = datetime.now(UTC)
647 timestamps = [
648@@ -1112,6 +1126,103 @@
649 self.assertEqual(0, store.find(Product,
650 Product._information_type == None).count())
651
652+ def test_PopulateLatestPersonSourcepackageReleaseCache(self):
653+ switch_dbuser('testadmin')
654+ # Make some same test data - we create published source package
655+ # releases for 2 different creators and maintainers.
656+ creators = []
657+ for _ in range(2):
658+ creators.append(self.factory.makePerson())
659+ maintainers = []
660+ for _ in range(2):
661+ maintainers.append(self.factory.makePerson())
662+
663+ spn = self.factory.makeSourcePackageName()
664+ distroseries = self.factory.makeDistroSeries()
665+ spr1 = self.factory.makeSourcePackageRelease(
666+ creator=creators[0], maintainer=maintainers[0],
667+ distroseries=distroseries, sourcepackagename=spn,
668+ date_uploaded=datetime(2010, 12, 1, tzinfo=pytz.UTC))
669+ self.factory.makeSourcePackagePublishingHistory(
670+ status=PackagePublishingStatus.PUBLISHED,
671+ sourcepackagerelease=spr1)
672+ spr2 = self.factory.makeSourcePackageRelease(
673+ creator=creators[0], maintainer=maintainers[1],
674+ distroseries=distroseries, sourcepackagename=spn,
675+ date_uploaded=datetime(2010, 12, 2, tzinfo=pytz.UTC))
676+ self.factory.makeSourcePackagePublishingHistory(
677+ status=PackagePublishingStatus.PUBLISHED,
678+ sourcepackagerelease=spr2)
679+ spr3 = self.factory.makeSourcePackageRelease(
680+ creator=creators[1], maintainer=maintainers[0],
681+ distroseries=distroseries, sourcepackagename=spn,
682+ date_uploaded=datetime(2010, 12, 3, tzinfo=pytz.UTC))
683+ self.factory.makeSourcePackagePublishingHistory(
684+ status=PackagePublishingStatus.PUBLISHED,
685+ sourcepackagerelease=spr3)
686+ spr4 = self.factory.makeSourcePackageRelease(
687+ creator=creators[1], maintainer=maintainers[1],
688+ distroseries=distroseries, sourcepackagename=spn,
689+ date_uploaded=datetime(2010, 12, 4, tzinfo=pytz.UTC))
690+ self.factory.makeSourcePackagePublishingHistory(
691+ status=PackagePublishingStatus.PUBLISHED,
692+ sourcepackagerelease=spr4)
693+
694+ transaction.commit()
695+ self.runFrequently()
696+
697+ store = IMasterStore(LatestPersonSourcepackageReleaseCache)
698+ # Check that the garbo state table has data.
699+ self.assertIsNotNone(
700+ store.execute(
701+ 'SELECT * FROM GarboJobState WHERE name=?',
702+ params=[u'PopulateLatestPersonSourcepackageReleaseCache']
703+ ).get_one())
704+
705+ def _assert_release_by_creator(creator, spr):
706+ release_records = store.find(
707+ LatestPersonSourcepackageReleaseCache,
708+ LatestPersonSourcepackageReleaseCache.creator == creator)
709+ [record] = list(release_records)
710+ self.assertEqual(spr.creator, record.creator)
711+ self.assertIsNone(record.maintainer)
712+ self.assertEqual(
713+ spr.dateuploaded, pytz.UTC.localize(record.dateuploaded))
714+
715+ def _assert_release_by_maintainer(maintainer, spr):
716+ release_records = store.find(
717+ LatestPersonSourcepackageReleaseCache,
718+ LatestPersonSourcepackageReleaseCache.maintainer == maintainer)
719+ [record] = list(release_records)
720+ self.assertEqual(spr.maintainer, record.maintainer)
721+ self.assertIsNone(record.creator)
722+ self.assertEqual(
723+ spr.dateuploaded, pytz.UTC.localize(record.dateuploaded))
724+
725+ _assert_release_by_creator(creators[0], spr2)
726+ _assert_release_by_creator(creators[1], spr4)
727+ _assert_release_by_maintainer(maintainers[0], spr3)
728+ _assert_release_by_maintainer(maintainers[1], spr4)
729+
730+ # Create a newer published source package release and ensure the
731+ # release cache table is correctly updated.
732+ switch_dbuser('testadmin')
733+ spr5 = self.factory.makeSourcePackageRelease(
734+ creator=creators[1], maintainer=maintainers[1],
735+ distroseries=distroseries, sourcepackagename=spn,
736+ date_uploaded=datetime(2010, 12, 5, tzinfo=pytz.UTC))
737+ self.factory.makeSourcePackagePublishingHistory(
738+ status=PackagePublishingStatus.PUBLISHED,
739+ sourcepackagerelease=spr5)
740+
741+ transaction.commit()
742+ self.runFrequently()
743+
744+ _assert_release_by_creator(creators[0], spr2)
745+ _assert_release_by_creator(creators[1], spr5)
746+ _assert_release_by_maintainer(maintainers[0], spr3)
747+ _assert_release_by_maintainer(maintainers[1], spr5)
748+
749
750 class TestGarboTasks(TestCaseWithFactory):
751 layer = LaunchpadZopelessLayer
752
753=== modified file 'lib/lp/soyuz/configure.zcml'
754--- lib/lp/soyuz/configure.zcml 2012-09-28 14:48:20 +0000
755+++ lib/lp/soyuz/configure.zcml 2012-11-06 08:03:49 +0000
756@@ -1002,6 +1002,12 @@
757 <allow interface="lp.soyuz.adapters.overrides.IOverridePolicy" />
758 </class>
759
760+ <class
761+ class="lp.soyuz.model.reporting.LatestPersonSourcepackageReleaseCache">
762+ <allow
763+ interface="lp.soyuz.interfaces.reporting.ILatestPersonSourcepackageReleaseCache"/>
764+ </class>
765+
766 <!-- ProcessAcceptedBugsJobSource -->
767 <securedutility
768 component=".model.processacceptedbugsjob.ProcessAcceptedBugsJob"
769
770=== added file 'lib/lp/soyuz/interfaces/reporting.py'
771--- lib/lp/soyuz/interfaces/reporting.py 1970-01-01 00:00:00 +0000
772+++ lib/lp/soyuz/interfaces/reporting.py 2012-11-06 08:03:49 +0000
773@@ -0,0 +1,30 @@
774+# Copyright 2012 Canonical Ltd. This software is licensed under the
775+# GNU Affero General Public License version 3 (see the file LICENSE).
776+
777+__metaclass__ = type
778+__all__ = [
779+ 'ILatestPersonSourcepackageReleaseCache',
780+ ]
781+
782+
783+from zope.interface import Attribute
784+from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease
785+
786+
787+class ILatestPersonSourcepackageReleaseCache(ISourcePackageRelease):
788+ """Published source package release information for a person.
789+
790+ The records represented by this object are the latest published source
791+ package releases for a given sourcepackage, distroseries, archive, keyed
792+ on the package creator and maintainer. The table contains a set of data
793+ records for package creators and a second set of data for package
794+ maintainers. Queries can be filtered by creator or maintainer as required.
795+ """
796+
797+ id = Attribute(
798+ "The id of the associated LatestPersonSourcepackageReleaseCache"
799+ "record.")
800+ sourcepackagerelease = Attribute(
801+ "The SourcePackageRelease which this object represents.")
802+ publication = Attribute(
803+ "The publication record for the associated SourcePackageRelease.")
804
805=== added file 'lib/lp/soyuz/model/reporting.py'
806--- lib/lp/soyuz/model/reporting.py 1970-01-01 00:00:00 +0000
807+++ lib/lp/soyuz/model/reporting.py 2012-11-06 08:03:49 +0000
808@@ -0,0 +1,49 @@
809+# Copyright 2012 Canonical Ltd. This software is licensed under the
810+# GNU Affero General Public License version 3 (see the file LICENSE).
811+
812+__metaclass__ = type
813+__all__ = [
814+ 'LatestPersonSourcepackageReleaseCache',
815+ ]
816+
817+from lazr.delegates import delegates
818+from storm.base import Storm
819+from storm.locals import (
820+ Int,
821+ Reference,
822+ )
823+from storm.properties import DateTime
824+from zope.interface import implements
825+
826+from lp.services.database.enumcol import EnumCol
827+from lp.soyuz.enums import ArchivePurpose
828+from lp.soyuz.interfaces.reporting import (
829+ ILatestPersonSourcepackageReleaseCache,
830+ )
831+from lp.soyuz.interfaces.sourcepackagerelease import ISourcePackageRelease
832+
833+
834+class LatestPersonSourcepackageReleaseCache(Storm):
835+ """See `LatestPersonSourcepackageReleaseCache`."""
836+ implements(ILatestPersonSourcepackageReleaseCache)
837+ delegates(ISourcePackageRelease, context='sourcepackagerelease')
838+
839+ __storm_table__ = 'LatestPersonSourcepackageReleaseCache'
840+
841+ id = Int(name='id', primary=True)
842+ publication_id = Int(name='publication')
843+ publication = Reference(
844+ publication_id, 'SourcePackagePublishingHistory.id')
845+ dateuploaded = DateTime(name='date_uploaded')
846+ creator_id = Int(name='creator')
847+ maintainer_id = Int(name='maintainer')
848+ upload_archive_id = Int(name='upload_archive')
849+ upload_archive = Reference(upload_archive_id, 'Archive.id')
850+ archive_purpose = EnumCol(schema=ArchivePurpose)
851+ upload_distroseries_id = Int(name='upload_distroseries')
852+ upload_distroseries = Reference(upload_distroseries_id, 'DistroSeries.id')
853+ sourcepackagename_id = Int(name='sourcepackagename')
854+ sourcepackagename = Reference(sourcepackagename_id, 'SourcePackageName.id')
855+ sourcepackagerelease_id = Int(name='sourcepackagerelease')
856+ sourcepackagerelease = Reference(
857+ sourcepackagerelease_id, 'SourcePackageRelease.id')