launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #22872
[Merge] lp:~cjwatson/launchpad/better-gitref-bulk-loading into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/better-gitref-bulk-loading into lp:launchpad.
Commit message:
Use a more compact query to load objects with compound primary keys.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/better-gitref-bulk-loading/+merge/354112
Mentioned in passing in bug 1790047, although it's not really essential to that bug.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/better-gitref-bulk-loading into lp:launchpad.
=== modified file 'lib/lp/services/database/bulk.py'
--- lib/lp/services/database/bulk.py 2013-08-23 05:36:46 +0000
+++ lib/lp/services/database/bulk.py 2018-08-31 13:05:56 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2018 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Optimized bulk operations against the database."""
@@ -16,7 +16,10 @@
from collections import defaultdict
from functools import partial
-from itertools import chain
+from itertools import (
+ chain,
+ groupby,
+ )
from operator import (
attrgetter,
itemgetter,
@@ -99,6 +102,26 @@
return primary_key
+def _make_compound_load_clause(primary_key, values_list):
+ """Construct a bulk-loading query clause for a compound primary key.
+
+ When the primary key is compound, we expect that in practice we will
+ often want to load objects with common leading elements of the key: for
+ example, we often want to load many `GitRef` objects from the same
+ repository. This helper returns a query clause optimised to be compact
+ in this case.
+ """
+ if len(primary_key) > 1:
+ return Or(*(
+ And(
+ primary_key[0] == leading_value,
+ _make_compound_load_clause(
+ primary_key[1:], [values[1:] for values in group]))
+ for leading_value, group in groupby(values_list, itemgetter(0))))
+ else:
+ return primary_key[0].is_in([values[0] for values in values_list])
+
+
def load(object_type, primary_keys, store=None):
"""Load a large number of objects efficiently."""
primary_key = _primary_key(object_type, allow_compound=True)
@@ -107,9 +130,8 @@
if not primary_keys:
return []
if isinstance(primary_key, tuple):
- condition = Or(*(
- And(*(key == value for (key, value) in zip(primary_key, values)))
- for values in primary_keys))
+ condition = _make_compound_load_clause(
+ primary_key, sorted(primary_keys))
else:
condition = primary_key.is_in(primary_keys)
if store is None:
=== modified file 'lib/lp/services/database/tests/test_bulk.py'
--- lib/lp/services/database/tests/test_bulk.py 2018-04-19 00:02:19 +0000
+++ lib/lp/services/database/tests/test_bulk.py 2018-08-31 13:05:56 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2018 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Test the bulk database functions."""
@@ -35,7 +35,10 @@
ISlaveStore,
IStore,
)
-from lp.services.database.sqlbase import get_transaction_timestamp
+from lp.services.database.sqlbase import (
+ convert_storm_clause_to_string,
+ get_transaction_timestamp,
+ )
from lp.services.features.model import (
FeatureFlag,
getFeatureStore,
@@ -172,6 +175,27 @@
bulk.reload([db_object])
self.assertIsNone(db_object_info.get('invalidated'))
+ def test__make_compound_load_clause(self):
+ # The query constructed by _make_compound_load_clause has the
+ # correct structure.
+ clause = bulk._make_compound_load_clause(
+ # This primary key is fictional, but lets us do a more elaborate
+ # test.
+ (FeatureFlag.scope, FeatureFlag.priority, FeatureFlag.flag),
+ sorted(
+ [(u'foo', 0, u'bar'), (u'foo', 0, u'baz'),
+ (u'foo', 1, u'bar'), (u'foo', 1, u'quux'),
+ (u'bar', 0, u'foo')]))
+ self.assertEqual(
+ "FeatureFlag.scope = E'bar' AND ("
+ "FeatureFlag.priority = 0 AND FeatureFlag.flag IN (E'foo')) OR "
+ "FeatureFlag.scope = E'foo' AND ("
+ "FeatureFlag.priority = 0 AND "
+ "FeatureFlag.flag IN (E'bar', E'baz') OR "
+ "FeatureFlag.priority = 1 AND "
+ "FeatureFlag.flag IN (E'bar', E'quux'))",
+ convert_storm_clause_to_string(clause))
+
def test_load(self):
# load() loads objects of the given type by their primary keys.
db_objects = [
@@ -189,7 +213,7 @@
ClassInfoError, bulk.load, str, [])
def test_load_with_compound_primary_keys(self):
- # load() does not like compound primary keys.
+ # load() can load objects with compound primary keys.
flags = [
FeatureFlag(u'foo', 0, u'bar', u'true'),
FeatureFlag(u'foo', 0, u'baz', u'false'),
Follow ups