Skip to content

Commit cdfe851

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 a095146 commit cdfe851

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:
@@ -407,6 +407,19 @@ def __init__(self, model, *args, **kw):
407407
self._patch = None
408408
self._it = chunks(self._ids, self._chunk_size, fmt=self._browse)
409409

410+
def _values_query(self, query):
411+
cr = self._model.env.cr
412+
cr.execute(format_query(cr, "WITH query AS ({}) SELECT count(*) FROM query", SQLStr(query)))
413+
size = cr.fetchone()[0]
414+
415+
def get_values():
416+
with named_cursor(cr, itersize=self._chunk_size) as ncr:
417+
ncr.execute(SQLStr(query))
418+
for row in ncr.iterdict():
419+
yield row
420+
421+
return size, get_values()
422+
410423
def _browse(self, ids):
411424
next(self._end(), None)
412425
args = self._cr_uid + (list(ids),)
@@ -457,35 +470,47 @@ def caller(*args, **kwargs):
457470
self._it = None
458471
return caller
459472

460-
def create(self, values, **kw):
473+
def create(self, values=None, query=None, **kw):
461474
"""
462475
Create records.
463476
464477
An alternative to the default `create` method of the ORM that is safe to use to
465478
create millions of records.
466479
467-
:param list(dict) values: list of values of the records to create
480+
:param iterable(dict) values: iterable of values of the records to create
481+
:param int size: the no. of elements produced by values, required if values is a generator
482+
:param str query: alternative to values, SQL query that can produce them.
483+
*No* DML statements allowed. Only SELECT.
468484
:param bool multi: whether to use the multi version of `create`, by default is
469485
`True` from Odoo 12 and above
470486
"""
471487
multi = kw.pop("multi", version_gte("saas~11.5"))
488+
size = kw.pop("size", None)
472489
if kw:
473490
raise TypeError("Unknown arguments: %s" % ", ".join(kw))
474491

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

478495
if self._size:
479496
raise ValueError("`create` can only called on empty `browse_record` objects.")
480497

481-
ids = []
482-
size = len(values)
498+
if query:
499+
size, values = self._values_query(query)
500+
501+
if size is None:
502+
try:
503+
size = len(values)
504+
except TypeError:
505+
raise ValueError("When passing a generator of values, the size kwarg is mandatory")
506+
483507
it = chunks(values, self._chunk_size, fmt=list)
484508
if self._logger:
485509
sz = (size + self._chunk_size - 1) // self._chunk_size
486510
qualifier = "env[%r].create([:%d])" % (self._model._name, self._chunk_size)
487511
it = log_progress(it, self._logger, qualifier=qualifier, size=sz)
488512

513+
ids = []
489514
self._patch = no_selection_cache_validation()
490515
for sub_values in it:
491516
self._patch.start()

0 commit comments

Comments
 (0)