|
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: |
@@ -422,6 +422,19 @@ def __init__(self, model, *args, **kw): |
422 | 422 | self._patch = None |
423 | 423 | self._it = chunks(self._ids, self._chunk_size, fmt=self._browse) |
424 | 424 |
|
| 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 | + |
425 | 438 | def _browse(self, ids): |
426 | 439 | next(self._end(), None) |
427 | 440 | args = self._cr_uid + (list(ids),) |
@@ -473,35 +486,47 @@ def caller(*args, **kwargs): |
473 | 486 | self._it = None |
474 | 487 | return caller |
475 | 488 |
|
476 | | - def create(self, values, **kw): |
| 489 | + def create(self, values=None, query=None, **kw): |
477 | 490 | """ |
478 | 491 | Create records. |
479 | 492 |
|
480 | 493 | An alternative to the default `create` method of the ORM that is safe to use to |
481 | 494 | create millions of records. |
482 | 495 |
|
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. |
484 | 500 | :param bool multi: whether to use the multi version of `create`, by default is |
485 | 501 | `True` from Odoo 12 and above |
486 | 502 | """ |
487 | 503 | multi = kw.pop("multi", version_gte("saas~11.5")) |
| 504 | + size = kw.pop("size", None) |
488 | 505 | if kw: |
489 | 506 | raise TypeError("Unknown arguments: %s" % ", ".join(kw)) |
490 | 507 |
|
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") |
493 | 510 |
|
494 | 511 | if self._size: |
495 | 512 | raise ValueError("`create` can only called on empty `browse_record` objects.") |
496 | 513 |
|
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 | + |
499 | 523 | it = chunks(values, self._chunk_size, fmt=list) |
500 | 524 | if self._logger: |
501 | 525 | sz = (size + self._chunk_size - 1) // self._chunk_size |
502 | 526 | qualifier = "env[%r].create([:%d])" % (self._model._name, self._chunk_size) |
503 | 527 | it = log_progress(it, self._logger, qualifier=qualifier, size=sz) |
504 | 528 |
|
| 529 | + ids = [] |
505 | 530 | self._patch = no_selection_cache_validation() |
506 | 531 | for sub_values in it: |
507 | 532 | self._patch.start() |
|
0 commit comments