Skip to content

Commit ad0d9e5

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 2162f8e commit ad0d9e5

File tree

1 file changed

+32
-7
lines changed

1 file changed

+32
-7
lines changed

src/util/orm.py

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
from .exceptions import MigrationError
4343
from .helpers import table_of_model
4444
from .misc import chunks, log_progress, version_between, version_gte
45-
from .pg import SQLStr, column_exists, format_query, get_columns, query_ids
45+
from .pg import SQLStr, column_exists, format_query, get_columns, named_cursor, query_ids
4646

4747
# python3 shims
4848
try:
@@ -422,6 +422,19 @@ def __init__(self, model, *args, **kw):
422422
self._patch = None
423423
self._it = chunks(self._ids, self._chunk_size, fmt=self._browse)
424424

425+
def _values_query(self, query):
426+
cr = self._model.env.cr
427+
cr.execute(format_query(cr, "WITH query AS ({}) SELECT count(*) FROM query", SQLStr(query)))
428+
size = cr.fetchone()[0]
429+
430+
def get_values():
431+
with named_cursor(cr, itersize=self._chunk_size) as ncr:
432+
ncr.execute(SQLStr(query))
433+
for row in ncr.iterdict():
434+
yield row
435+
436+
return size, get_values()
437+
425438
def _browse(self, ids):
426439
next(self._end(), None)
427440
args = self._cr_uid + (list(ids),)
@@ -473,35 +486,47 @@ def caller(*args, **kwargs):
473486
self._it = None
474487
return caller
475488

476-
def create(self, values, **kw):
489+
def create(self, values=None, query=None, **kw):
477490
"""
478491
Create records.
479492
480493
An alternative to the default `create` method of the ORM that is safe to use to
481494
create millions of records.
482495
483-
:param list(dict) values: list of values of the records to create
496+
:param iterable(dict) values: iterable of values of the records to create
497+
:param int size: the no. of elements produced by values, required if values is a generator
498+
:param str query: alternative to values, SQL query that can produce them.
499+
*No* DML statements allowed. Only SELECT.
484500
:param bool multi: whether to use the multi version of `create`, by default is
485501
`True` from Odoo 12 and above
486502
"""
487503
multi = kw.pop("multi", version_gte("saas~11.5"))
504+
size = kw.pop("size", None)
488505
if kw:
489506
raise TypeError("Unknown arguments: %s" % ", ".join(kw))
490507

491-
if not values:
492-
raise ValueError("`create` cannot be called with an empty `values` argument")
508+
if not (values is None) ^ (query is None):
509+
raise ValueError("`create` needs to be called using exactly one of `values` or `query` arguments")
493510

494511
if self._size:
495512
raise ValueError("`create` can only called on empty `browse_record` objects.")
496513

497-
ids = []
498-
size = len(values)
514+
if query:
515+
size, values = self._values_query(query)
516+
517+
if size is None:
518+
try:
519+
size = len(values)
520+
except TypeError:
521+
raise ValueError("When passing a generator of values, the size kwarg is mandatory")
522+
499523
it = chunks(values, self._chunk_size, fmt=list)
500524
if self._logger:
501525
sz = (size + self._chunk_size - 1) // self._chunk_size
502526
qualifier = "env[%r].create([:%d])" % (self._model._name, self._chunk_size)
503527
it = log_progress(it, self._logger, qualifier=qualifier, size=sz)
504528

529+
ids = []
505530
self._patch = no_selection_cache_validation()
506531
for sub_values in it:
507532
self._patch.start()

0 commit comments

Comments
 (0)