[FIX] models: avoid prefetch hell when reading fields

The method `read()` may be very slow when reading relational fields and
computed fields, because the computed fields can be computed on a recordset
that is larger than expected.

The issue occurs on model 'res.partner' when reading fields 'child_ids' and
'purchase_order_count', for instance.  Suppose we read those two fields on a
partner with 1000 contacts.  First, the one2many field is read from the
database and stored to the cache; the latter adds the value ids to the
prefetching of 'res.partner'.  Then, the fields are fetched from the cache.
When 'purchase_order_count' is accessed on the partner, the field is computed
on all its children as well...

closes odoo/odoo#29867
This commit is contained in:
Raphael Collet
2019-01-03 12:45:48 +00:00
parent 9ef7802520
commit a07a076c45
2 changed files with 37 additions and 10 deletions
+25
View File
@@ -358,6 +358,31 @@ class TestAPI(common.TransactionCase):
same_prefetch(empty, empty.bank_ids)
same_prefetch(empty, empty.category_id)
@mute_logger('odoo.models')
def test_60_prefetch_read(self):
""" Check that reading a field computes it on self only. """
Partner = self.env['res.partner']
field = type(Partner).company_type
self.assertTrue(field.compute and not field.store)
partner1 = Partner.create({'name': 'Foo'})
partner2 = Partner.create({'name': 'Bar', 'parent_id': partner1.id})
self.assertEqual(partner1.child_ids, partner2)
# reading partner1 should not prefetch 'company_type' on partner2
self.env.clear()
partner1 = partner1.with_prefetch()
partner1.read(['company_type'])
self.assertIn('company_type', partner1._cache)
self.assertNotIn('company_type', partner2._cache)
# reading partner1 should not prefetch 'company_type' on partner2
self.env.clear()
partner1 = partner1.with_prefetch()
partner1.read(['child_ids', 'company_type'])
self.assertIn('company_type', partner1._cache)
self.assertNotIn('company_type', partner2._cache)
@mute_logger('odoo.models')
def test_70_one(self):
""" Check method one(). """
+12 -10
View File
@@ -3019,17 +3019,19 @@ class BaseModel(object):
# retrieve results from records; this takes values from the cache and
# computes remaining fields
result = []
name_fields = [(name, self._fields[name]) for name in (stored + inherited + computed)]
data = {record: {'id': record.id} for record in self}
missing = set()
use_name_get = (load == '_classic_read')
for record in self:
try:
values = {'id': record.id}
for name, field in name_fields:
values[name] = field.convert_to_read(record[name], record, use_name_get)
result.append(values)
except MissingError:
pass
for name in (stored + inherited + computed):
convert = self._fields[name].convert_to_read
# read every field with prefetching limited to self; this avoids
# computing fields on a larger recordset than self
for record in self.with_prefetch():
try:
data[record][name] = convert(record[name], record, use_name_get)
except MissingError:
missing.add(record)
result = [data[record] for record in self if record not in missing]
return result