1111
1212import logging
1313import re
14+ import uuid
1415from contextlib import contextmanager
1516from functools import wraps
1617from itertools import chain
@@ -374,7 +375,9 @@ class iter_browse(object):
374375
375376 :param model: the model to iterate
376377 :type model: :class:`odoo.model.Model`
377- :param list(int) ids: list of IDs of the records to iterate
378+ :param iterable(int) ids: iterable of IDs of the records to iterate
379+ :param str query: alternative to ids, SQL query that can produce them.
380+ Can also be a DML statement with a RETURNING clause.
378381 :param int chunk_size: number of records to load in each iteration chunk, `200` by
379382 default
380383 :param logger: logger used to report the progress, by default
@@ -387,23 +390,60 @@ class iter_browse(object):
387390 See also :func:`~odoo.upgrade.util.orm.env`
388391 """
389392
390- __slots__ = ("_chunk_size" , "_cr_uid" , "_it" , "_logger" , "_model" , "_patch" , "_size" , "_strategy" )
393+ __slots__ = ("_chunk_size" , "_cr_uid" , "_ids" , " _it" , "_logger" , "_model" , "_patch" , "_query " , "_size" , "_strategy" )
391394
392395 def __init__ (self , model , * args , ** kw ):
393396 assert len (args ) in [1 , 3 ] # either (cr, uid, ids) or (ids,)
394397 self ._model = model
395398 self ._cr_uid = args [:- 1 ]
396- ids = args [- 1 ]
397- self ._size = len (ids )
399+ self ._ids = args [- 1 ]
400+ self ._size = kw .pop ("size" , None )
401+ self ._query = kw .pop ("query" , None )
398402 self ._chunk_size = kw .pop ("chunk_size" , 200 ) # keyword-only argument
399403 self ._logger = kw .pop ("logger" , _logger )
400404 self ._strategy = kw .pop ("strategy" , "flush" )
401405 assert self ._strategy in {"flush" , "commit" }
402406 if kw :
403407 raise TypeError ("Unknown arguments: %s" % ", " .join (kw ))
404408
409+ if not (self ._ids is None ) ^ (self ._query is None ):
410+ raise TypeError ("Must be initialized using exactly one of `ids` or `query`" )
411+
412+ if self ._query :
413+ self ._ids_query ()
414+
415+ if not self ._size :
416+ try :
417+ self ._size = len (self ._ids )
418+ except TypeError :
419+ raise ValueError ("When passing ids as a generator, the size kwarg is mandatory" )
405420 self ._patch = None
406- self ._it = chunks (ids , self ._chunk_size , fmt = self ._browse )
421+ self ._it = chunks (self ._ids , self ._chunk_size , fmt = self ._browse )
422+
423+ def _ids_query (self ):
424+ cr = self ._model .env .cr
425+ tmp_tbl = "_upgrade_ib_{}" .format (uuid .uuid4 ().hex )
426+ cr .execute (
427+ format_query (
428+ cr ,
429+ "CREATE UNLOGGED TABLE {}(id) AS (WITH query AS ({}) SELECT * FROM query)" ,
430+ tmp_tbl ,
431+ SQLStr (self ._query ),
432+ )
433+ )
434+ self ._size = cr .rowcount
435+ cr .execute (
436+ format_query (cr , "ALTER TABLE {} ADD CONSTRAINT {} PRIMARY KEY (id)" , tmp_tbl , "pk_{}_id" .format (tmp_tbl ))
437+ )
438+
439+ def get_ids ():
440+ with named_cursor (cr , itersize = self ._chunk_size ) as ncr :
441+ ncr .execute (format_query (cr , "SELECT id FROM {} ORDER BY id" , tmp_tbl ))
442+ for (id_ ,) in ncr :
443+ yield id_
444+ cr .execute (format_query (cr , "DROP TABLE IF EXISTS {}" , tmp_tbl ))
445+
446+ self ._ids = get_ids ()
407447
408448 def _browse (self , ids ):
409449 next (self ._end (), None )
0 commit comments