[IMP] models: in _read_from_database(), fetch inherited fields when possible

This should improve the performance of method read() on models with inherited
fields, like product.product.  The inherited fields that are stored as columns
in parent tables (except for translated fields) are read in the same query as
the fields of the model.  Those fields will be directly stored in cache under
the main model, so that no copying will take place in cache for accessing them
(this is the default implementation of inherited fields).
This commit is contained in:
Raphael Collet 2015-02-12 16:06:21 +01:00
parent 2c261a2987
commit 04ba0e99a4
1 changed files with 39 additions and 17 deletions

View File

@ -3146,17 +3146,20 @@ class BaseModel(object):
fields = self.check_field_access_rights('read', fields)
# split fields into stored and computed fields
stored, computed = [], []
stored, inherited, computed = [], [], []
for name in fields:
if name in self._columns:
stored.append(name)
elif name in self._fields:
computed.append(name)
field = self._fields[name]
if field.inherited and field.base_field.column:
inherited.append(name)
else:
_logger.warning("%s.read() with unknown field '%s'", self._name, name)
# fetch stored fields from the database to the cache
self._read_from_database(stored)
self._read_from_database(stored, inherited)
# retrieve results from records; this takes values from the cache and
# computes remaining fields
@ -3225,9 +3228,14 @@ class BaseModel(object):
self._cache[field] = FailedValue(e)
@api.multi
def _read_from_database(self, field_names):
def _read_from_database(self, field_names, inherited_field_names=[]):
""" Read the given fields of the records in `self` from the database,
and store them in cache. Access errors are also stored in cache.
:param field_names: list of column names of model `self`; all those
fields are guaranteed to be read
:param inherited_field_names: list of column names from parent
models; some of those fields may not be read
"""
env = self.env
cr, user, context = env.args
@ -3237,18 +3245,29 @@ class BaseModel(object):
self._apply_ir_rules(query, 'read')
order_str = self._generate_order_by(None, query)
# determine the fields that are stored as columns in self._table
fields_pre = [f for f in field_names if self._columns[f]._classic_write]
# determine the fields that are stored as columns in tables;
# for the sake of simplicity, discard inherited translated fields
fields = map(self._fields.get, field_names + inherited_field_names)
fields_pre = [
field
for field in fields
if field.base_field.column._classic_write
if not (field.inherited and field.base_field.column.translate)
]
# the query may involve several tables: we need fully-qualified names
def qualify(f):
if isinstance(self._columns.get(f), fields.binary) and \
context.get('bin_size_%s' % f, context.get('bin_size')):
# PG 9.2 introduces conflicting pg_size_pretty(numeric) -> need ::cast
return 'pg_size_pretty(length(%s."%s")::bigint) as "%s"' % (self._table, f, f)
def qualify(field):
col = field.name
if field.inherited:
res = self._inherits_join_calc(field.name, query)
else:
return '%s."%s"' % (self._table, f)
qual_names = map(qualify, set(fields_pre + ['id']))
res = '"%s"."%s"' % (self._table, col)
if field.type == 'binary' and (context.get('bin_size') or context.get('bin_size_' + col)):
# PG 9.2 introduces conflicting pg_size_pretty(numeric) -> need ::cast
res = 'pg_size_pretty(length(%s)::bigint) as "%s"' % (res, col)
return res
qual_names = map(qualify, set(fields_pre + [self._fields['id']]))
# determine the actual query to execute
from_clause, where_clause, where_params = query.get_sql()
@ -3272,8 +3291,9 @@ class BaseModel(object):
# translate the fields if necessary
if context.get('lang'):
ir_translation = env['ir.translation']
for f in fields_pre:
if self._columns[f].translate:
for field in fields_pre:
if not field.inherited and field.column.translate:
f = field.name
#TODO: optimize out of this loop
res_trans = ir_translation._get_ids(
'%s,%s' % (self._name, f), 'model', context['lang'], ids)
@ -3281,9 +3301,10 @@ class BaseModel(object):
vals[f] = res_trans.get(vals['id'], False) or vals[f]
# apply the symbol_get functions of the fields we just read
for f in fields_pre:
symbol_get = self._columns[f]._symbol_get
for field in fields_pre:
symbol_get = field.base_field.column._symbol_get
if symbol_get:
f = field.name
for vals in result:
vals[f] = symbol_get(vals[f])
@ -3292,7 +3313,8 @@ class BaseModel(object):
record = self.browse(vals['id'])
record._cache.update(record._convert_to_cache(vals, validate=False))
# determine the fields that must be processed now
# determine the fields that must be processed now;
# for the sake of simplicity, we ignore inherited fields
fields_post = [f for f in field_names if not self._columns[f]._classic_write]
# Compute POST fields, grouped by multi