[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:
@@ -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
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user