1111
1212import logging
1313import re
14+ import uuid
1415from contextlib import contextmanager
1516from functools import wraps
1617from itertools import chain
@@ -374,7 +375,8 @@ 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
378380 :param int chunk_size: number of records to load in each iteration chunk, `200` by
379381 default
380382 :param logger: logger used to report the progress, by default
@@ -387,23 +389,60 @@ class iter_browse(object):
387389 See also :func:`~odoo.upgrade.util.orm.env`
388390 """
389391
390- __slots__ = ("_chunk_size" , "_cr_uid" , "_it" , "_logger" , "_model" , "_patch" , "_size" , "_strategy" )
392+ __slots__ = ("_chunk_size" , "_cr_uid" , "_ids" , " _it" , "_logger" , "_model" , "_patch" , "_query " , "_size" , "_strategy" )
391393
392394 def __init__ (self , model , * args , ** kw ):
393395 assert len (args ) in [1 , 3 ] # either (cr, uid, ids) or (ids,)
394396 self ._model = model
395397 self ._cr_uid = args [:- 1 ]
396- ids = args [- 1 ]
397- self ._size = len (ids )
398+ self ._ids = args [- 1 ]
399+ self ._size = kw .pop ("size" , None )
400+ self ._query = kw .pop ("query" , None )
398401 self ._chunk_size = kw .pop ("chunk_size" , 200 ) # keyword-only argument
399402 self ._logger = kw .pop ("logger" , _logger )
400403 self ._strategy = kw .pop ("strategy" , "flush" )
401404 assert self ._strategy in {"flush" , "commit" }
402405 if kw :
403406 raise TypeError ("Unknown arguments: %s" % ", " .join (kw ))
404407
408+ if not bool (self ._ids ) ^ bool (self ._query ):
409+ raise TypeError ("Must be initialized using exactly one of `ids` or `query`" )
410+
411+ if self ._query :
412+ self ._ids_query ()
413+
414+ if not self ._size :
415+ try :
416+ self ._size = len (self ._ids )
417+ except TypeError :
418+ raise ValueError ("When passing ids as a generator, the size kwarg is mandatory" )
405419 self ._patch = None
406- self ._it = chunks (ids , self ._chunk_size , fmt = self ._browse )
420+ self ._it = chunks (self ._ids , self ._chunk_size , fmt = self ._browse )
421+
422+ def _ids_query (self ):
423+ cr = self ._model .env .cr
424+ tmp_tbl = "_upgrade_ib_{}" .format (uuid .uuid4 ().hex )
425+ cr .execute (
426+ format_query (
427+ cr ,
428+ "CREATE UNLOGGED TABLE {}(id) AS (WITH query AS ({}) SELECT * FROM query)" ,
429+ tmp_tbl ,
430+ SQLStr (self ._query ),
431+ )
432+ )
433+ self ._size = cr .rowcount
434+ cr .execute (
435+ format_query (cr , "ALTER TABLE {} ADD CONSTRAINT {} PRIMARY KEY (id)" , tmp_tbl , "pk_{}_id" .format (tmp_tbl ))
436+ )
437+
438+ def get_ids ():
439+ with named_cursor (cr , itersize = self ._chunk_size ) as ncr :
440+ ncr .execute (format_query (cr , "SELECT id FROM {} ORDER BY id" , tmp_tbl ))
441+ for (id_ ,) in ncr :
442+ yield id_
443+ cr .execute (format_query (cr , "DROP TABLE IF EXISTS {}" , tmp_tbl ))
444+
445+ self ._ids = get_ids ()
407446
408447 def _browse (self , ids ):
409448 next (self ._end (), None )
0 commit comments