Skip to content

Commit 13adede

Browse files
committed
[IMP] util/orm: recompute_fields add query parameter
In most cases when we pass a list of `ids` to `recompute_fields` we just run a query to get them. The main downside of this approach is when the list is too big: it can cause MemoryErrors. Instead we can let `recompute_fields` use a query to get the ids and take the necessary precautions to not fetch millions of entries all at once. As a nice side effect code becomes easier to review since we don't need to check the fetching of ids is done correctly. closes #317 Related: odoo/upgrade#8426 Signed-off-by: Christophe Simonis (chs) <chs@odoo.com>
1 parent b910e43 commit 13adede

File tree

1 file changed

+30
-16
lines changed

1 file changed

+30
-16
lines changed

src/util/orm.py

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def wrapper(*args, **kwargs):
251251

252252

253253
@no_selection_cache_validation
254-
def recompute_fields(cr, model, fields, ids=None, logger=_logger, chunk_size=256, strategy="auto"):
254+
def recompute_fields(cr, model, fields, ids=None, logger=_logger, chunk_size=256, strategy="auto", query=None):
255255
"""
256256
Recompute field values.
257257
@@ -271,40 +271,54 @@ def recompute_fields(cr, model, fields, ids=None, logger=_logger, chunk_size=256
271271
:param str model: name of the model to recompute
272272
:param list(str) fields: list of the name of the fields to recompute
273273
:param list(int) or None ids: list of the IDs of the records to recompute, when `None`
274-
recompute *all* records
274+
recompute *all* records, unless `query` is also set (see
275+
below)
275276
:param logger: logger used to report the progress
276277
:type logger: :class:`logging.Logger`
277278
:param int chunk_size: number of records per chunk - used to split the processing
278279
:param str strategy: strategy used to process the re-computation
280+
:param str query: query to get the IDs of records to recompute, it is an error to set
281+
both `ids` and `query`
279282
"""
280-
assert strategy in {"flush", "commit", "auto"}
283+
if strategy not in {"flush", "commit", "auto"}:
284+
raise ValueError("Invalid strategy {!r}".format(strategy))
285+
if ids is not None and query is not None:
286+
raise ValueError("Cannot set both `ids` and `query`")
281287
Model = env(cr)[model] if isinstance(model, basestring) else model
282288
model = Model._name
283289

284-
def get_ids():
285-
if ids is not None:
286-
for id_ in ids:
287-
yield id_
290+
if ids is None:
291+
query = format_query(cr, "SELECT id FROM {} ORDER BY id", table_of_model(cr, model)) if query is None else query
292+
cr.execute(query)
293+
count = cr.rowcount
294+
if count < 2**21: # avoid the overhead of a named cursor unless we have at least two chunks
295+
ids_ = (id_ for (id_,) in cr.fetchall())
288296
else:
289-
with named_cursor(cr, itersize=2**20) as ncr:
290-
ncr.execute(format_query(cr, "SELECT id FROM {t} ORDER BY id", t=table_of_model(cr, model)))
291-
for (id_,) in ncr:
292-
yield id_
293297

294-
if ids is None:
295-
cr.execute(format_query(cr, "SELECT COUNT(id) FROM {t}", t=table_of_model(cr, model)))
296-
(count,) = cr.fetchone()
298+
def get_ids():
299+
with named_cursor(cr, itersize=2**20) as ncr:
300+
ncr.execute(query)
301+
for (id_,) in ncr:
302+
yield id_
303+
304+
ids_ = get_ids()
297305
else:
298306
count = len(ids)
307+
ids_ = ids
308+
309+
if not count:
310+
return
311+
312+
_logger.info("Computing fields %s of %r on %d records", fields, model, count)
299313

300314
if strategy == "auto":
301315
big_table = count > BIG_TABLE_THRESHOLD
302316
any_tracked_field = any(getattr(Model._fields[f], _TRACKING_ATTR, False) for f in fields)
303317
strategy = "commit" if big_table and any_tracked_field else "flush"
304318

305319
size = (count + chunk_size - 1) / chunk_size
306-
qual = "%s %d-bucket" % (model, chunk_size) if chunk_size != 1 else model
307-
for subids in log_progress(chunks(get_ids(), chunk_size, list), logger, qualifier=qual, size=size):
320+
qual = "{} {:d}-bucket".format(model, chunk_size) if chunk_size != 1 else model
321+
for subids in log_progress(chunks(ids_, chunk_size, list), logger, qualifier=qual, size=size):
308322
records = Model.browse(subids)
309323
for field_name in fields:
310324
field = records._fields[field_name]

0 commit comments

Comments
 (0)