Merge lp:~wallyworld/launchpad/ppa-packages-timeout-1071581 into lp:launchpad
- ppa-packages-timeout-1071581
- Merge into devel
| 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 | ||||
| Related bugs: |
|
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Curtis Hovey (community) | code | 2012-11-02 | Approve on 2012-11-05 |
|
Review via email:
|
|||
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 LatestPersonSou
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/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
| Curtis Hovey (sinzui) wrote : Posted in a previous version of this proposal | # |
| 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
> SourcePackagePu
>
> 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.
Preview Diff
| 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') |

Line 123 looks inconsistent because the storm column does not use ID gePublishingHis tory.sourcepack agereleaseID
SourcePacka
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.