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
=== added file 'database/schema/patch-2209-77-1.sql'
--- database/schema/patch-2209-77-1.sql 1970-01-01 00:00:00 +0000
+++ database/schema/patch-2209-77-1.sql 2016-05-05 07:56:03 +0000
@@ -0,0 +1,42 @@
1-- Copyright 2016 Canonical Ltd. This software is licensed under the
2-- GNU Affero General Public License version 3 (see the file LICENSE).
3
4SET client_min_messages=ERROR;
5
6-- Add ID sort indexes.
7CREATE INDEX gitrepository__distribution__spn__id__idx
8 ON gitrepository(distribution, sourcepackagename, id)
9 WHERE distribution IS NOT NULL;
10CREATE INDEX gitrepository__owner__distribution__spn__id__idx
11 ON gitrepository(owner, distribution, sourcepackagename, id)
12 WHERE distribution IS NOT NULL;
13CREATE INDEX gitrepository__project__id__idx
14 ON gitrepository(project, id)
15 WHERE project IS NOT NULL;
16CREATE INDEX gitrepository__owner__project__id__idx
17 ON gitrepository(owner, project, id)
18 WHERE project IS NOT NULL;
19CREATE INDEX gitrepository__owner__id__idx
20 ON gitrepository(owner, id);
21
22-- Replace owner/target_default indexes with unique and partial ones.
23CREATE UNIQUE INDEX gitrepository__distribution__spn__target_default__key
24 ON gitrepository(distribution, sourcepackagename)
25 WHERE distribution IS NOT NULL AND target_default;
26DROP INDEX gitrepository__distribution__spn__target_default__idx;
27CREATE UNIQUE INDEX gitrepository__owner__distribution__spn__owner_default__key
28 ON gitrepository(owner, distribution, sourcepackagename)
29 WHERE distribution IS NOT NULL AND owner_default;
30DROP INDEX gitrepository__owner__distribution__spn__owner_default__idx;
31
32CREATE UNIQUE INDEX gitrepository__project__target_default__key
33 ON gitrepository(project)
34 WHERE project IS NOT NULL AND target_default;
35DROP INDEX gitrepository__project__target_default__idx;
36CREATE UNIQUE INDEX gitrepository__owner__project__owner_default__key
37 ON gitrepository(owner, project)
38 WHERE project IS NOT NULL AND owner_default;
39DROP INDEX gitrepository__owner__project__owner_default__idx;
40
41
42INSERT INTO LaunchpadDatabaseRevision VALUES (2209, 77, 1);
043
=== modified file 'lib/lp/code/interfaces/gitcollection.py'
--- lib/lp/code/interfaces/gitcollection.py 2015-10-12 12:58:32 +0000
+++ lib/lp/code/interfaces/gitcollection.py 2016-05-05 07:56:03 +0000
@@ -1,4 +1,4 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the1# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""A collection of Git repositories.4"""A collection of Git repositories.
@@ -54,7 +54,8 @@
54 collection.54 collection.
55 """55 """
5656
57 def getRepositories(eager_load=False, order_by_date=False):57 def getRepositories(eager_load=False, order_by_date=False,
58 order_by_id=False):
58 """Return a result set of all repositories in this collection.59 """Return a result set of all repositories in this collection.
5960
60 The returned result set will also join across the specified tables61 The returned result set will also join across the specified tables
@@ -66,6 +67,7 @@
66 objects in the collection.67 objects in the collection.
67 :param order_by_date: If True, order results by descending68 :param order_by_date: If True, order results by descending
68 modification date.69 modification date.
70 :param order_by_id: If True, order results by ascending ID.
69 """71 """
7072
71 def getRepositoryIds():73 def getRepositoryIds():
7274
=== modified file 'lib/lp/code/model/gitcollection.py'
--- lib/lp/code/model/gitcollection.py 2015-10-12 12:58:32 +0000
+++ lib/lp/code/model/gitcollection.py 2016-05-05 07:56:03 +0000
@@ -1,4 +1,4 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the1# Copyright 2015-2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Implementations of `IGitCollection`."""4"""Implementations of `IGitCollection`."""
@@ -199,15 +199,18 @@
199 load_related(Product, repositories, ['project_id'])199 load_related(Product, repositories, ['project_id'])
200200
201 def getRepositories(self, find_expr=GitRepository, eager_load=False,201 def getRepositories(self, find_expr=GitRepository, eager_load=False,
202 order_by_date=False):202 order_by_date=False, order_by_id=False):
203 """See `IGitCollection`."""203 """See `IGitCollection`."""
204 all_tables = set(204 all_tables = set(
205 self._tables.values() + self._asymmetric_tables.values())205 self._tables.values() + self._asymmetric_tables.values())
206 tables = [GitRepository] + list(all_tables)206 tables = [GitRepository] + list(all_tables)
207 expressions = self._getRepositoryExpressions()207 expressions = self._getRepositoryExpressions()
208 resultset = self.store.using(*tables).find(find_expr, *expressions)208 resultset = self.store.using(*tables).find(find_expr, *expressions)
209 assert not order_by_date or not order_by_id
209 if order_by_date:210 if order_by_date:
210 resultset.order_by(Desc(GitRepository.date_last_modified))211 resultset.order_by(Desc(GitRepository.date_last_modified))
212 elif order_by_id:
213 resultset.order_by(GitRepository.id)
211214
212 def do_eager_load(rows):215 def do_eager_load(rows):
213 repository_ids = set(repository.id for repository in rows)216 repository_ids = set(repository.id for repository in rows)
214217
=== modified file 'lib/lp/code/model/gitrepository.py'
--- lib/lp/code/model/gitrepository.py 2016-04-29 15:17:20 +0000
+++ lib/lp/code/model/gitrepository.py 2016-05-05 07:56:03 +0000
@@ -1221,7 +1221,7 @@
1221 def getRepositories(self, user, target):1221 def getRepositories(self, user, target):
1222 """See `IGitRepositorySet`."""1222 """See `IGitRepositorySet`."""
1223 collection = IGitCollection(target).visibleByUser(user)1223 collection = IGitCollection(target).visibleByUser(user)
1224 return collection.getRepositories(eager_load=True)1224 return collection.getRepositories(eager_load=True, order_by_id=True)
12251225
1226 def getRepositoryVisibilityInfo(self, user, person, repository_names):1226 def getRepositoryVisibilityInfo(self, user, person, repository_names):
1227 """See `IGitRepositorySet`."""1227 """See `IGitRepositorySet`."""
12281228
=== modified file 'lib/lp/code/model/tests/test_gitrepository.py'
--- lib/lp/code/model/tests/test_gitrepository.py 2016-05-03 15:12:46 +0000
+++ lib/lp/code/model/tests/test_gitrepository.py 2016-05-05 07:56:03 +0000
@@ -102,6 +102,7 @@
102 IAccessPolicyArtifactSource,102 IAccessPolicyArtifactSource,
103 IAccessPolicySource,103 IAccessPolicySource,
104 )104 )
105from lp.registry.interfaces.person import IPerson
105from lp.registry.interfaces.persondistributionsourcepackage import (106from lp.registry.interfaces.persondistributionsourcepackage import (
106 IPersonDistributionSourcePackageFactory,107 IPersonDistributionSourcePackageFactory,
107 )108 )
@@ -125,6 +126,7 @@
125 celebrity_logged_in,126 celebrity_logged_in,
126 login_person,127 login_person,
127 person_logged_in,128 person_logged_in,
129 record_two_runs,
128 TestCaseWithFactory,130 TestCaseWithFactory,
129 verifyObject,131 verifyObject,
130 )132 )
@@ -138,7 +140,10 @@
138 ZopelessDatabaseLayer,140 ZopelessDatabaseLayer,
139 )141 )
140from lp.testing.mail_helpers import pop_notifications142from lp.testing.mail_helpers import pop_notifications
141from lp.testing.matchers import DoesNotSnapshot143from lp.testing.matchers import (
144 DoesNotSnapshot,
145 HasQueryCount,
146 )
142from lp.testing.pages import webservice_for_person147from lp.testing.pages import webservice_for_person
143148
144149
@@ -2443,56 +2448,51 @@
2443 "git+ssh://git.launchpad.dev/~person/project/+git/repository",2448 "git+ssh://git.launchpad.dev/~person/project/+git/repository",
2444 repository["git_ssh_url"])2449 repository["git_ssh_url"])
24452450
2451 def assertGetRepositoriesWorks(self, target_db):
2452 if IPerson.providedBy(target_db):
2453 owner_db = target_db
2454 else:
2455 owner_db = self.factory.makePerson()
2456 owner_url = api_url(owner_db)
2457 target_url = api_url(target_db)
2458
2459 repos_db = []
2460 repos_url = []
2461
2462 webservice = webservice_for_person(
2463 owner_db, permission=OAuthPermission.WRITE_PUBLIC)
2464 webservice.default_api_version = "devel"
2465
2466 def create_repository():
2467 with admin_logged_in():
2468 repo = self.factory.makeGitRepository(
2469 target=target_db, owner=owner_db)
2470 repos_db.append(repo)
2471 repos_url.append(api_url(repo))
2472
2473 def verify_getRepositories():
2474 response = webservice.named_get(
2475 "/+git", "getRepositories", user=owner_url, target=target_url)
2476 self.assertEqual(200, response.status)
2477 self.assertEqual(
2478 [webservice.getAbsoluteUrl(url) for url in repos_url],
2479 [entry["self_link"]
2480 for entry in response.jsonBody()["entries"]])
2481
2482 verify_getRepositories()
2483 recorder1, recorder2 = record_two_runs(
2484 verify_getRepositories, create_repository, 2)
2485 self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
2486
2446 def test_getRepositories_project(self):2487 def test_getRepositories_project(self):
2447 project_db = self.factory.makeProduct()2488 self.assertGetRepositoriesWorks(self.factory.makeProduct())
2448 repository_db = self.factory.makeGitRepository(target=project_db)
2449 webservice = webservice_for_person(
2450 repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC)
2451 webservice.default_api_version = "devel"
2452 with person_logged_in(ANONYMOUS):
2453 repository_url = api_url(repository_db)
2454 owner_url = api_url(repository_db.owner)
2455 project_url = api_url(project_db)
2456 response = webservice.named_get(
2457 "/+git", "getRepositories", user=owner_url, target=project_url)
2458 self.assertEqual(200, response.status)
2459 self.assertEqual(
2460 [webservice.getAbsoluteUrl(repository_url)],
2461 [entry["self_link"] for entry in response.jsonBody()["entries"]])
24622489
2463 def test_getRepositories_package(self):2490 def test_getRepositories_package(self):
2464 dsp_db = self.factory.makeDistributionSourcePackage()2491 self.assertGetRepositoriesWorks(
2465 repository_db = self.factory.makeGitRepository(target=dsp_db)2492 self.factory.makeDistributionSourcePackage())
2466 webservice = webservice_for_person(
2467 repository_db.owner, permission=OAuthPermission.WRITE_PUBLIC)
2468 webservice.default_api_version = "devel"
2469 with person_logged_in(ANONYMOUS):
2470 repository_url = api_url(repository_db)
2471 owner_url = api_url(repository_db.owner)
2472 dsp_url = api_url(dsp_db)
2473 response = webservice.named_get(
2474 "/+git", "getRepositories", user=owner_url, target=dsp_url)
2475 self.assertEqual(200, response.status)
2476 self.assertEqual(
2477 [webservice.getAbsoluteUrl(repository_url)],
2478 [entry["self_link"] for entry in response.jsonBody()["entries"]])
24792493
2480 def test_getRepositories_personal(self):2494 def test_getRepositories_personal(self):
2481 owner_db = self.factory.makePerson()2495 self.assertGetRepositoriesWorks(self.factory.makePerson())
2482 repository_db = self.factory.makeGitRepository(
2483 owner=owner_db, target=owner_db)
2484 webservice = webservice_for_person(
2485 owner_db, permission=OAuthPermission.WRITE_PUBLIC)
2486 webservice.default_api_version = "devel"
2487 with person_logged_in(ANONYMOUS):
2488 repository_url = api_url(repository_db)
2489 owner_url = api_url(owner_db)
2490 response = webservice.named_get(
2491 "/+git", "getRepositories", user=owner_url, target=owner_url)
2492 self.assertEqual(200, response.status)
2493 self.assertEqual(
2494 [webservice.getAbsoluteUrl(repository_url)],
2495 [entry["self_link"] for entry in response.jsonBody()["entries"]])
24962496
2497 def test_set_information_type(self):2497 def test_set_information_type(self):
2498 # The repository owner can change the information type.2498 # The repository owner can change the information type.