Skip to content

Commit 5d515f7

Browse files
committed
[IMP] orm: iter_browse accept generator or query as ids
This allows the caller to be memory efficient on huge numbers of ids, allowing for even more millions of records to be browsed.
1 parent 60c49c5 commit 5d515f7

File tree

1 file changed

+37
-5
lines changed

1 file changed

+37
-5
lines changed

src/util/orm.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import logging
1313
import re
14+
import uuid
1415
from contextlib import contextmanager
1516
from functools import wraps
1617
from itertools import chain
@@ -374,7 +375,8 @@ class iter_browse(object):
374375
375376
:param model: the model to iterate
376377
:type model: :class:`odoo.model.Model`
377-
:param list(int) ids: list of IDs of the records to iterate
378+
:param iterable(int) or SQLStr ids: iterable of IDs of the records to iterate, or a SQL query
379+
that can produce the IDs
378380
:param int chunk_size: number of records to load in each iteration chunk, `200` by
379381
default
380382
:param logger: logger used to report the progress, by default
@@ -387,23 +389,53 @@ class iter_browse(object):
387389
See also :func:`~odoo.upgrade.util.orm.env`
388390
"""
389391

390-
__slots__ = ("_chunk_size", "_cr_uid", "_it", "_logger", "_model", "_patch", "_size", "_strategy")
392+
__slots__ = ("_chunk_size", "_cr_uid", "_ids", "_it", "_logger", "_model", "_patch", "_size", "_strategy")
391393

392394
def __init__(self, model, *args, **kw):
393395
assert len(args) in [1, 3] # either (cr, uid, ids) or (ids,)
394396
self._model = model
395397
self._cr_uid = args[:-1]
396-
ids = args[-1]
397-
self._size = len(ids)
398+
self._ids = args[-1]
399+
self._size = kw.pop("size", None)
398400
self._chunk_size = kw.pop("chunk_size", 200) # keyword-only argument
399401
self._logger = kw.pop("logger", _logger)
400402
self._strategy = kw.pop("strategy", "flush")
401403
assert self._strategy in {"flush", "commit"}
402404
if kw:
403405
raise TypeError("Unknown arguments: %s" % ", ".join(kw))
404406

407+
if isinstance(self._ids, SQLStr):
408+
self._init_ids_query()
409+
410+
if not self._size:
411+
try:
412+
self._size = len(self._ids)
413+
except TypeError:
414+
raise ValueError("When passing ids as a generator, the size kwarg is mandatory")
405415
self._patch = None
406-
self._it = chunks(ids, self._chunk_size, fmt=self._browse)
416+
self._it = chunks(self._ids, self._chunk_size, fmt=self._browse)
417+
418+
def _init_ids_query(self):
419+
cr = self._model.env.cr
420+
tmp_tbl = "_upgrade_ib_{}".format(uuid.uuid4().hex)
421+
cr.execute(
422+
format_query(
423+
cr, "CREATE UNLOGGED TABLE {}(id) AS (WITH query AS ({}) SELECT * FROM query)", tmp_tbl, self._ids
424+
)
425+
)
426+
self._size = cr.rowcount
427+
cr.execute(
428+
format_query(cr, "ALTER TABLE {} ADD CONSTRAINT {} PRIMARY KEY (id)", tmp_tbl, "pk_{}_id".format(tmp_tbl))
429+
)
430+
431+
def get_ids():
432+
with named_cursor(cr, itersize=self._chunk_size) as ncr:
433+
ncr.execute(format_query(cr, "SELECT id FROM {} ORDER BY id", tmp_tbl))
434+
for (id_,) in ncr:
435+
yield id_
436+
cr.execute(format_query(cr, "DROP TABLE IF EXISTS {}", tmp_tbl))
437+
438+
self._ids = get_ids()
407439

408440
def _browse(self, ids):
409441
next(self._end(), None)

0 commit comments

Comments
 (0)