[FIX] core: prefetching of context-dependent fields

Consider a context-dependent field, and successively access it on a
recordset with different contexts.  On the first context, the field is
correctly computed in batch.  After that, the field is always computed
one by one.

The bug is in the method that determines which records in a given set
have no value in cache.  On the first context, the cache is empty for
the field, so all records are returned.  After that, the method
considers that all records have a value in cache: they do, but for
another context key!  Simply using the context key when looking up the
cache fixes the issue.

closes odoo/odoo#52360

X-original-commit: 35d69589d9b43afe6c6fc9779458323f7180153e
Signed-off-by: Xavier Dollé (xdo) <xdo@odoo.com>
Co-authored-by: Xavier-Do <xdo@odoo.com>
This commit is contained in:
Raphael Collet
2020-06-03 13:00:36 +00:00
committed by Xavier-Do
co-authored by Xavier-Do
parent 1d0501d261
commit 6d5da2d2d3
3 changed files with 40 additions and 4 deletions
@@ -11,6 +11,7 @@ class BaseModel(models.Model):
name = fields.Char()
value = fields.Integer(default=0)
value_pc = fields.Float(compute="_value_pc", store=True)
value_ctx = fields.Float(compute="_value_ctx")
partner_id = fields.Many2one('res.partner', string='Customer')
line_ids = fields.One2many('test_performance.line', 'base_id')
@@ -22,6 +23,12 @@ class BaseModel(models.Model):
for record in self:
record.value_pc = float(record.value) / 100
@api.depends_context('key')
def _value_ctx(self):
self.env.cr.execute('SELECT 42') # one dummy query per batch
for record in self:
record.value_ctx = self.env.context.get('key')
@api.depends('line_ids.value')
def _total(self):
for record in self:
@@ -65,6 +65,24 @@ class TestPerformance(SavepointCaseWithUserDemo):
for record in records:
record.value_pc
@warmup
def test_read_base_depends_context(self):
""" Compute in batch even when in cache in another context. """
records = self.env['test_performance.base'].search([])
self.assertEqual(len(records), 5)
with self.assertQueryCount(1):
for record in records.with_context(key=1):
self.assertEqual(record.value_ctx, 1)
with self.assertQueryCount(1):
for record in records.with_context(key=2):
self.assertEqual(record.value_ctx, 2)
with self.assertQueryCount(1):
for record in records:
self.assertEqual(record.with_context(key=3).value_ctx, 3)
@users('__system__', 'demo')
@warmup
def test_write_base(self):
+15 -4
View File
@@ -878,15 +878,26 @@ class Cache(object):
def get_records(self, model, field):
""" Return the records of ``model`` that have a value for ``field``. """
ids = list(self._data[field])
field_cache = self._data[field]
if field.depends_context:
key = model.env.cache_key(field)
ids = [id_ for id_, value in field_cache.items() if key in value]
else:
ids = list(field_cache)
return model.browse(ids)
def get_missing_ids(self, records, field):
""" Return the ids of ``records`` that have no value for ``field``. """
field_cache = self._data[field]
for record_id in records._ids:
if record_id not in field_cache:
yield record_id
if field.depends_context:
key = records.env.cache_key(field)
for record_id in records._ids:
if key not in field_cache.get(record_id, ()):
yield record_id
else:
for record_id in records._ids:
if record_id not in field_cache:
yield record_id
def invalidate(self, spec=None):
""" Invalidate the cache, partially or totally depending on ``spec``. """