|
42 | 42 | from .exceptions import MigrationError |
43 | 43 | from .helpers import table_of_model |
44 | 44 | 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 |
46 | 46 |
|
47 | 47 | # python3 shims |
48 | 48 | try: |
@@ -407,6 +407,19 @@ def __init__(self, model, *args, **kw): |
407 | 407 | self._patch = None |
408 | 408 | self._it = chunks(self._ids, self._chunk_size, fmt=self._browse) |
409 | 409 |
|
| 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 | + |
410 | 423 | def _browse(self, ids): |
411 | 424 | next(self._end(), None) |
412 | 425 | args = self._cr_uid + (list(ids),) |
@@ -457,35 +470,47 @@ def caller(*args, **kwargs): |
457 | 470 | self._it = None |
458 | 471 | return caller |
459 | 472 |
|
460 | | - def create(self, values, **kw): |
| 473 | + def create(self, values=None, query=None, **kw): |
461 | 474 | """ |
462 | 475 | Create records. |
463 | 476 |
|
464 | 477 | An alternative to the default `create` method of the ORM that is safe to use to |
465 | 478 | create millions of records. |
466 | 479 |
|
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. |
468 | 484 | :param bool multi: whether to use the multi version of `create`, by default is |
469 | 485 | `True` from Odoo 12 and above |
470 | 486 | """ |
471 | 487 | multi = kw.pop("multi", version_gte("saas~11.5")) |
| 488 | + size = kw.pop("size", None) |
472 | 489 | if kw: |
473 | 490 | raise TypeError("Unknown arguments: %s" % ", ".join(kw)) |
474 | 491 |
|
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") |
477 | 494 |
|
478 | 495 | if self._size: |
479 | 496 | raise ValueError("`create` can only called on empty `browse_record` objects.") |
480 | 497 |
|
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 | + |
483 | 507 | it = chunks(values, self._chunk_size, fmt=list) |
484 | 508 | if self._logger: |
485 | 509 | sz = (size + self._chunk_size - 1) // self._chunk_size |
486 | 510 | qualifier = "env[%r].create([:%d])" % (self._model._name, self._chunk_size) |
487 | 511 | it = log_progress(it, self._logger, qualifier=qualifier, size=sz) |
488 | 512 |
|
| 513 | + ids = [] |
489 | 514 | self._patch = no_selection_cache_validation() |
490 | 515 | for sub_values in it: |
491 | 516 | self._patch.start() |
|
0 commit comments