@@ -1932,3 +1932,81 @@ def bulk_update_table(cr, table, columns, mapping, key_col="id"):
19321932 key_col = key_col ,
19331933 )
19341934 cr .execute (query , [Json (mapping )])
1935+
1936+
1937+ class query_ids (object ):
1938+ """
1939+ Iterator over ids returned by a query.
1940+
1941+ This iterator can memory efficiently query a potentially huge number of ids.
1942+
1943+ :param str query: the query that returns the ids. It can be DML, e.g. `UPDATE table WHERE ... RETURNING id`.
1944+ :param int itersize: passed to a named_cursor, determines the number of rows fetched from PG at once.
1945+ """
1946+
1947+ def __init__ (self , cr , query , itersize = None ):
1948+ self ._ncr = None
1949+ self ._cr = cr
1950+ self ._tmp_tbl = "_upgrade_query_ids_{}" .format (uuid .uuid4 ().hex )
1951+ cr .commit ()
1952+ cr .execute (
1953+ format_query (
1954+ cr ,
1955+ "CREATE UNLOGGED TABLE {}(id) AS (WITH query AS ({}) SELECT * FROM query)" ,
1956+ self ._tmp_tbl ,
1957+ SQLStr (query ),
1958+ )
1959+ )
1960+ self ._len = cr .rowcount
1961+ try :
1962+ cr .execute (
1963+ format_query (
1964+ cr ,
1965+ "ALTER TABLE {} ADD CONSTRAINT {} PRIMARY KEY (id)" ,
1966+ self ._tmp_tbl ,
1967+ "pk_{}_id" .format (self ._tmp_tbl ),
1968+ )
1969+ )
1970+ except psycopg2 .IntegrityError as e :
1971+ if e .pgcode == errorcodes .UNIQUE_VIOLATION :
1972+ cr .rollback ()
1973+ raise ValueError ("The query for ids is producing duplicate values: {}" .format (query ))
1974+ raise
1975+ self ._ncr = named_cursor (cr , itersize )
1976+ self ._ncr .execute (format_query (cr , "SELECT id FROM {} ORDER BY id" , self ._tmp_tbl ))
1977+ self ._it = iter (self ._ncr )
1978+
1979+ def _close (self ):
1980+ if self ._ncr :
1981+ if self ._ncr .closed :
1982+ return
1983+ self ._ncr .close ()
1984+ self ._cr .execute (format_query (self ._cr , "DROP TABLE IF EXISTS {}" , self ._tmp_tbl ))
1985+
1986+ def __len__ (self ):
1987+ return self ._len
1988+
1989+ def __iter__ (self ):
1990+ return self
1991+
1992+ def __next__ (self ):
1993+ if self ._ncr .closed :
1994+ raise StopIteration
1995+ try :
1996+ return next (self ._it )[0 ]
1997+ except StopIteration :
1998+ self ._close ()
1999+ raise
2000+
2001+ def next (self ):
2002+ return self .__next__ ()
2003+
2004+ def __enter__ (self ):
2005+ return self
2006+
2007+ def __exit__ (self , exc_type , exc_value , traceback ):
2008+ self ._close ()
2009+ return False
2010+
2011+ def __del__ (self ):
2012+ self ._close ()
0 commit comments