Skip to content

Commit d3e1776

Browse files
committed
[IMP] orm: iter_browse.create() accept generator or query as values
Done to be able to create millions of records memory-efficiently.
1 parent 9ad9ca4 commit d3e1776

File tree

1 file changed

+31
-6
lines changed

1 file changed

+31
-6
lines changed

src/util/orm.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,19 @@ def get_ids():
445445

446446
self._ids = get_ids()
447447

448+
def _values_query(self, query):
449+
cr = self._model.env.cr
450+
cr.execute(format_query(cr, "WITH query AS ({}) SELECT count(*) FROM query", SQLStr(query)))
451+
size = cr.fetchone()[0]
452+
453+
def get_values():
454+
with named_cursor(cr, itersize=self._chunk_size) as ncr:
455+
ncr.execute(SQLStr(query))
456+
for row in ncr.iterdict():
457+
yield row
458+
459+
return size, get_values()
460+
448461
def _browse(self, ids):
449462
next(self._end(), None)
450463
args = self._cr_uid + (list(ids),)
@@ -495,35 +508,47 @@ def caller(*args, **kwargs):
495508
self._it = None
496509
return caller
497510

498-
def create(self, values, **kw):
511+
def create(self, values=None, query=None, **kw):
499512
"""
500513
Create records.
501514
502515
An alternative to the default `create` method of the ORM that is safe to use to
503516
create millions of records.
504517
505-
:param list(dict) values: list of values of the records to create
518+
:param iterable(dict) values: iterable of values of the records to create
519+
:param int size: the no. of elements produced by values, required if values is a generator
520+
:param str query: alternative to values, SQL query that can produce them.
521+
*No* DML statements allowed. Only SELECT.
506522
:param bool multi: whether to use the multi version of `create`, by default is
507523
`True` from Odoo 12 and above
508524
"""
509525
multi = kw.pop("multi", version_gte("saas~11.5"))
526+
size = kw.pop("size", None)
510527
if kw:
511528
raise TypeError("Unknown arguments: %s" % ", ".join(kw))
512529

513-
if not values:
514-
raise ValueError("`create` cannot be called with an empty `values` argument")
530+
if not (values is None) ^ (query is None):
531+
raise ValueError("`create` needs to be called using exactly one of `values` or `query` arguments")
515532

516533
if self._size:
517534
raise ValueError("`create` can only called on empty `browse_record` objects.")
518535

519-
ids = []
520-
size = len(values)
536+
if query:
537+
size, values = self._values_query(query)
538+
539+
if size is None:
540+
try:
541+
size = len(values)
542+
except TypeError:
543+
raise ValueError("When passing a generator of values, the size kwarg is mandatory")
544+
521545
it = chunks(values, self._chunk_size, fmt=list)
522546
if self._logger:
523547
sz = (size + self._chunk_size - 1) // self._chunk_size
524548
qualifier = "env[%r].create([:%d])" % (self._model._name, self._chunk_size)
525549
it = log_progress(it, self._logger, qualifier=qualifier, size=sz)
526550

551+
ids = []
527552
self._patch = no_selection_cache_validation()
528553
for sub_values in it:
529554
self._patch.start()

0 commit comments

Comments
 (0)