Merge lp:~wgrant/launchpad/bug-1578205 into lp:launchpad

Proposed by William Grant
Status: Merged
Merged at revision: 18023
Proposed branch: lp:~wgrant/launchpad/bug-1578205
Merge into: lp:launchpad
Diff against target: 246 lines (+98/-51)
5 files modified
database/schema/patch-2209-77-1.sql (+42/-0)
lib/lp/code/interfaces/gitcollection.py (+4/-2)
lib/lp/code/model/gitcollection.py (+5/-2)
lib/lp/code/model/gitrepository.py (+1/-1)
lib/lp/code/model/tests/test_gitrepository.py (+46/-46)
To merge this branch: bzr merge lp:~wgrant/launchpad/bug-1578205
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+293857@code.launchpad.net

Commit message

Sort GitRepositorySet.getRepositories API results to make batching reliable.

Description of the change

Sort GitRepositorySet.getRepositories API results to make batching reliable.

Also add indexes for this new sort order, and while we're at it recreate the target/owner_default indexes to be unique and correctly partial.

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'database/schema/patch-2209-77-1.sql'
2--- database/schema/patch-2209-77-1.sql 1970-01-01 00:00:00 +0000
3+++ database/schema/patch-2209-77-1.sql 2016-05-05 07:56:03 +0000
4@@ -0,0 +1,42 @@
5+-- Copyright 2016 Canonical Ltd. This software is licensed under the
6+-- GNU Affero General Public License version 3 (see the file LICENSE).
7+
8+SET client_min_messages=ERROR;
9+
10+-- Add ID sort indexes.
11+CREATE INDEX gitrepository__distribution__spn__id__idx
12+ ON gitrepository(distribution, sourcepackagename, id)
13+ WHERE distribution IS NOT NULL;
14+CREATE INDEX gitrepository__owner__distribution__spn__id__idx
15+ ON gitrepository(owner, distribution, sourcepackagename, id)
16+ WHERE distribution IS NOT NULL;
17+CREATE INDEX gitrepository__project__id__idx
18+ ON gitrepository(project, id)
19+ WHERE project IS NOT NULL;
20+CREATE INDEX gitrepository__owner__project__id__idx
21+ ON gitrepository(owner, project, id)
22+ WHERE project IS NOT NULL;
23+CREATE INDEX gitrepository__owner__id__idx
24+ ON gitrepository(owner, id);
25+
26+-- Replace owner/target_default indexes with unique and partial ones.
27+CREATE UNIQUE INDEX gitrepository__distribution__spn__target_default__key
28+ ON gitrepository(distribution, sourcepackagename)
29+ WHERE distribution IS NOT NULL AND target_default;
30+DROP INDEX gitrepository__distribution__spn__target_default__idx;
31+CREATE UNIQUE INDEX gitrepository__owner__distribution__spn__owner_default__key
32+ ON gitrepository(owner, distribution, sourcepackagename)
33+ WHERE distribution IS NOT NULL AND owner_default;
34+DROP INDEX gitrepository__owner__distribution__spn__owner_default__idx;
35+
36+CREATE UNIQUE INDEX gitrepository__project__target_default__key
37+ ON gitrepository(project)
38+ WHERE project IS NOT NULL AND target_default;
39+DROP INDEX gitrepository__project__target_default__idx;
40+CREATE UNIQUE INDEX gitrepository__owner__project__owner_default__key
41+ ON gitrepository(owner, project)
42+ WHERE project IS NOT NULL AND owner_default;
43+DROP INDEX gitrepository__owner__project__owner_default__idx;
44+
45+
46+INSERT INTO LaunchpadDatabaseRevision VALUES (2209, 77, 1);
47
48=== modified file 'lib/lp/code/interfaces/gitcollection.py'
49--- lib/lp/code/interfaces/gitcollection.py 2015-10-12 12:58:32 +0000
50+++ lib/lp/code/interfaces/gitcollection.py 2016-05-05 07:56:03 +0000
51@@ -1,4 +1,4 @@
52-# Copyright 2015 Canonical Ltd. This software is licensed under the
53+# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
54 # GNU Affero General Public License version 3 (see the file LICENSE).
55
56 """A collection of Git repositories.
57@@ -54,7 +54,8 @@
58 collection.
59 """
60
61- def getRepositories(eager_load=False, order_by_date=False):
62+ def getRepositories(eager_load=False, order_by_date=False,
63+ order_by_id=False):
64 """Return a result set of all repositories in this collection.
65
66 The returned result set will also join across the specified tables
67@@ -66,6 +67,7 @@
68 objects in the collection.
69 :param order_by_date: If True, order results by descending
70 modification date.
71+ :param order_by_id: If True, order results by ascending ID.
72 """
73
74 def getRepositoryIds():
75
76=== modified file 'lib/lp/code/model/gitcollection.py'
77--- lib/lp/code/model/gitcollection.py 2015-10-12 12:58:32 +0000
78+++ lib/lp/code/model/gitcollection.py 2016-05-05 07:56:03 +0000
79@@ -1,4 +1,4 @@
80-# Copyright 2015 Canonical Ltd. This software is licensed under the
81+# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
82 # GNU Affero General Public License version 3 (see the file LICENSE).
83
84 """Implementations of `IGitCollection`."""
85@@ -199,15 +199,18 @@
86 load_related(Product, repositories, ['project_id'])
87
88 def getRepositories(self, find_expr=GitRepository, eager_load=False,
89- order_by_date=False):
90+ order_by_date=False, order_by_id=False):
91 """See `IGitCollection`."""
92 all_tables = set(
93 self._tables.values() + self._asymmetric_tables.values())
94 tables = [GitRepository] + list(all_tables)
95 expressions = self._getRepositoryExpressions()
96 resultset = self.store.using(*tables).find(find_expr, *expressions)
97+ assert not order_by_date or not order_by_id
98 if order_by_date:
99 resultset.order_by(Desc(GitRepository.date_last_modified))
100+ elif order_by_id:
101+ resultset.order_by(GitRepository.id)
102
103 def do_eager_load(rows):
104 repository_ids = set(repository.id for repository in rows)
105
106=== modified file 'lib/lp/code/model/gitrepository.py'
107--- lib/lp/code/model/gitrepository.py 2016-04-29 15:17:20 +0000
108+++ lib/lp/code/model/gitrepository.py 2016-05-05 07:56:03 +0000
109@@ -1221,7 +1221,7 @@
110 def getRepositories(self, user, target):
111 """See `IGitRepositorySet`."""
112 collection = IGitCollection(target).visibleByUser(user)
113- return collection.getRepositories(eager_load=True)
114+ return collection.getRepositories(eager_load=True, order_by_id=True)
115
116 def getRepositoryVisibilityInfo(self, user, person, repository_names):
117 """See `IGitRepositorySet`."""
118
119=== modified file 'lib/lp/code/model/tests/test_gitrepository.py'
120--- lib/lp/code/model/tests/test_gitrepository.py 2016-05-03 15:12:46 +0000
121+++ lib/lp/code/model/tests/test_gitrepository.py 2016-05-05 07:56:03 +0000
122@@ -102,6 +102,7 @@
123 IAccessPolicyArtifactSource,
124 IAccessPolicySource,
125 )
126+from lp.registry.interfaces.person import IPerson
127 from lp.registry.interfaces.persondistributionsourcepackage import (
128 IPersonDistributionSourcePackageFactory,
129 )
130@@ -125,6 +126,7 @@
131 celebrity_logged_in,
132 login_person,
133 person_logged_in,
134+ record_two_runs,
135 TestCaseWithFactory,
136 verifyObject,
137 )
138@@ -138,7 +140,10 @@
139 ZopelessDatabaseLayer,
140 )
141 from lp.testing.mail_helpers import pop_notifications
142-from lp.testing.matchers import DoesNotSnapshot
143+from lp.testing.matchers import (
144+ DoesNotSnapshot,
145+ HasQueryCount,
146+ )
147 from lp.testing.pages import webservice_for_person
148
149
150@@ -2443,56 +2448,51 @@
151 "git+ssh://git.launchpad.dev/~person/project/+git/repository",
152 repository["git_ssh_url"])
153
154+ def assertGetRepositoriesWorks(self, target_db):
155+ if IPerson.providedBy(target_db):
156+ owner_db = target_db
157+ else:
158+ owner_db = self.factory.makePerson()
159+ owner_url = api_url(owner_db)
160+ target_url = api_url(target_db)
161+
162+ repos_db = []
163+ repos_url = []
164+
165+ webservice = webservice_for_person(
166+ owner_db, permission=OAuthPermission.WRITE_PUBLIC)
167+ webservice.default_api_version = "devel"
168+
169+ def create_repository():
170+ with admin_logged_in():
171+ repo = self.factory.makeGitRepository(
172+ target=target_db, owner=owner_db)
173+ repos_db.append(repo)
174+ repos_url.append(api_url(repo))
175+
176+ def verify_getRepositories():
177+ response = webservice.named_get(
178+ "/+git", "getRepositories", user=owner_url, target=target_url)
179+ self.assertEqual(200, response.status)
180+ self.assertEqual(
181+ [webservice.getAbsoluteUrl(url) for url in repos_url],
182+ [entry["self_link"]
183+ for entry in response.jsonBody()["entries"]])
184+
185+ verify_getRepositories()
186+ recorder1, recorder2 = record_two_runs(
187+ verify_getRepositories, create_repository, 2)
188+ self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
189+
190 def test_getRepositories_project(self):
191- project_db = self.factory.makeProduct()
192- repository_db = self.factory.makeGitRepository(target=project_db)
193- webservice = webservice_for_person(
194- repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC)
195- webservice.default_api_version = "devel"
196- with person_logged_in(ANONYMOUS):
197- repository_url = api_url(repository_db)
198- owner_url = api_url(repository_db.owner)
199- project_url = api_url(project_db)
200- response = webservice.named_get(
201- "/+git", "getRepositories", user=owner_url, target=project_url)
202- self.assertEqual(200, response.status)
203- self.assertEqual(
204- [webservice.getAbsoluteUrl(repository_url)],
205- [entry["self_link"] for entry in response.jsonBody()["entries"]])
206+ self.assertGetRepositoriesWorks(self.factory.makeProduct())
207
208 def test_getRepositories_package(self):
209- dsp_db = self.factory.makeDistributionSourcePackage()
210- repository_db = self.factory.makeGitRepository(target=dsp_db)
211- webservice = webservice_for_person(
212- repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC)
213- webservice.default_api_version = "devel"
214- with person_logged_in(ANONYMOUS):
215- repository_url = api_url(repository_db)
216- owner_url = api_url(repository_db.owner)
217- dsp_url = api_url(dsp_db)
218- response = webservice.named_get(
219- "/+git", "getRepositories", user=owner_url, target=dsp_url)
220- self.assertEqual(200, response.status)
221- self.assertEqual(
222- [webservice.getAbsoluteUrl(repository_url)],
223- [entry["self_link"] for entry in response.jsonBody()["entries"]])
224+ self.assertGetRepositoriesWorks(
225+ self.factory.makeDistributionSourcePackage())
226
227 def test_getRepositories_personal(self):
228- owner_db = self.factory.makePerson()
229- repository_db = self.factory.makeGitRepository(
230- owner=owner_db, target=owner_db)
231- webservice = webservice_for_person(
232- owner_db, permission=OAuthPermission.WRITE_PUBLIC)
233- webservice.default_api_version = "devel"
234- with person_logged_in(ANONYMOUS):
235- repository_url = api_url(repository_db)
236- owner_url = api_url(owner_db)
237- response = webservice.named_get(
238- "/+git", "getRepositories", user=owner_url, target=owner_url)
239- self.assertEqual(200, response.status)
240- self.assertEqual(
241- [webservice.getAbsoluteUrl(repository_url)],
242- [entry["self_link"] for entry in response.jsonBody()["entries"]])
243+ self.assertGetRepositoriesWorks(self.factory.makePerson())
244
245 def test_set_information_type(self):
246 # The repository owner can change the information type.