Remove grouped_slice per IN_MAX

The usage of ``grouped_slice`` without size is deprecated since `=
ANY(ARRAY[%s])` is used for PostgreSQL and SQLite is used only for tests.

The ``Database.IN_MAX`` is replaced by ``backend.MAX_QUERY_PARAMS`` which is a
more accurate limit of the backend.
The SQLite limits is indeed SQLITE_MAX_EXPR_DEPTH so we use a empirical value.
This commit is contained in:
Cédric Krier
2026-03-22 21:05:26 +01:00
parent 0da67bce21
commit 5310b00496
98 changed files with 1799 additions and 2180 deletions
+5 -6
View File
@@ -11,7 +11,7 @@ from trytond.model import (
from trytond.model.exceptions import AccessError
from trytond.pool import Pool
from trytond.pyson import Bool, Eval, If
from trytond.tools import grouped_slice, is_full_text, lstrip_wildcard
from trytond.tools import is_full_text, lstrip_wildcard
from trytond.transaction import Transaction
from trytond.wizard import Button, StateTransition, StateView, Wizard
@@ -257,11 +257,10 @@ class Inventory(Workflow, ModelSQL, ModelView, ChatMixin):
else:
product_ids = [l.product.id for l in inventory.lines]
pbl = defaultdict(int)
for product_ids in grouped_slice(product_ids):
pbl.update(Product.products_by_location(
[inventory.location.id],
grouping=grouping,
grouping_filter=(list(product_ids),)))
pbl.update(Product.products_by_location(
[inventory.location.id],
grouping=grouping,
grouping_filter=(list(product_ids),)))
# Update existing lines
for line in inventory.lines:
+17 -22
View File
@@ -14,7 +14,6 @@ from trytond.model import (
from trytond.modules.product import price_digits, round_price
from trytond.pool import Pool
from trytond.pyson import Eval, If, TimeDelta
from trytond.tools import grouped_slice
from trytond.transaction import (
Transaction, inactive_records, without_check_access)
@@ -314,18 +313,16 @@ class Location(DeactivableMixin, tree(), ModelSQL, ModelView):
for (location_id, product), quantity in quantities.items():
if quantity and product not in consumables:
empty.discard(location_id)
for sub_ids in grouped_slice(list(empty)):
sub_ids = list(sub_ids)
moves = Move.search([
('state', 'not in', ['done', 'cancelled']),
['OR',
('from_location', 'in', sub_ids),
('to_location', 'in', sub_ids),
],
])
for move in moves:
for location in [move.from_location, move.to_location]:
empty.discard(location.id)
moves = Move.search([
('state', 'not in', ['done', 'cancelled']),
['OR',
('from_location', 'in', empty),
('to_location', 'in', empty),
],
])
for move in moves:
for location in [move.from_location, move.to_location]:
empty.discard(location.id)
return cls.browse(empty)
@staticmethod
@@ -434,15 +431,13 @@ class Location(DeactivableMixin, tree(), ModelSQL, ModelView):
if not grouping:
return {loc.id: None for loc in locations}
pbl = {}
for sub_locations in grouped_slice(locations):
location_ids = [l.id for l in sub_locations]
with Transaction().set_context(context):
pbl.update(Product.products_by_location(
location_ids,
grouping=grouping,
grouping_filter=grouping_filter,
with_childs=trans_context.get('with_childs', True)))
location_ids = [l.id for l in locations]
with Transaction().set_context(context):
pbl = Product.products_by_location(
location_ids,
grouping=grouping,
grouping_filter=grouping_filter,
with_childs=trans_context.get('with_childs', True))
return dict((loc.id, pbl.get((loc.id,) + key, 0)) for loc in locations)
-3
View File
@@ -13,9 +13,6 @@ this repository contains the full copyright notices and license terms. -->
<field name="text">You cannot change the cost price of a product which is associated with stock moves.
You must use the "Modify Cost Price" wizard.</field>
</record>
<record model="ir.message" id="msg_product_quantities_max">
<field name="text">You cannot select more than %(max)s products to compute quantities.</field>
</record>
<record model="ir.message" id="msg_invalid_cost_price">
<field name="text">Invalid cost price "%(cost_price)s" for product "%(product)s" with exception "%(exception)s".</field>
</record>
+16 -19
View File
@@ -19,7 +19,7 @@ from trytond.model.exceptions import AccessError
from trytond.modules.product import price_digits, round_price
from trytond.pool import Pool
from trytond.pyson import Bool, Eval, Id, If, TimeDelta
from trytond.tools import cached_property, grouped_slice
from trytond.tools import cached_property
from trytond.transaction import Transaction, without_check_access
from .exceptions import MoveFutureWarning, MoveOriginWarning
@@ -1314,7 +1314,6 @@ class Move(Workflow, ModelSQL, ModelView):
connection = transaction.connection
if database.has_select_for():
count = database.IN_MAX // 2
PeriodCache = Period.get_cache(grouping)
with connection.cursor() as cursor:
for company_id in company_ids:
@@ -1327,24 +1326,22 @@ class Move(Workflow, ModelSQL, ModelView):
], order=[('date', 'DESC')], limit=1)
if periods:
period, = periods
for sub_location_ids in grouped_slice(location_ids, count):
sub_location_ids = list(sub_location_ids)
table = cls.__table__()
query = table.select(Literal(1),
where=(fields.SQL_OPERATORS['in'](
table.to_location, sub_location_ids)
| fields.SQL_OPERATORS['in'](
table.from_location, sub_location_ids))
& table.product.in_(product_ids)
& (table.company == company_id),
for_=For('UPDATE', nowait=True))
table = cls.__table__()
query = table.select(Literal(1),
where=(fields.SQL_OPERATORS['in'](
table.to_location, location_ids)
| fields.SQL_OPERATORS['in'](
table.from_location, location_ids))
& table.product.in_(product_ids)
& (table.company == company_id),
for_=For('UPDATE', nowait=True))
if period:
query.where &= Coalesce(
table.effective_date,
table.planned_date,
datetime.date.max) > period.date
cursor.execute(*query)
if period:
query.where &= Coalesce(
table.effective_date,
table.planned_date,
datetime.date.max) > period.date
cursor.execute(*query)
else:
cls.lock()
+5 -9
View File
@@ -1,6 +1,6 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from itertools import chain, groupby
from itertools import groupby
from sql import For, Literal
@@ -8,7 +8,6 @@ from trytond.i18n import gettext
from trytond.model import Index, ModelSQL, ModelView, Workflow, fields
from trytond.pool import Pool
from trytond.pyson import Eval
from trytond.tools import grouped_slice
from trytond.transaction import Transaction
from .exceptions import PeriodCloseError
@@ -89,13 +88,10 @@ class Period(Workflow, ModelSQL, ModelView):
def draft(cls, periods):
for grouping in cls.groupings():
Cache = cls.get_cache(grouping)
caches = []
for sub_periods in grouped_slice(periods):
caches.append(Cache.search([
('period', 'in',
[p.id for p in sub_periods]),
], order=[]))
Cache.delete(list(chain(*caches)))
caches = Cache.search([
('period', 'in', periods),
], order=[])
Cache.delete(caches)
@classmethod
@ModelView.button
+32 -56
View File
@@ -17,7 +17,7 @@ from trytond.model.exceptions import AccessError
from trytond.modules.product import price_digits, round_price
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval, If, PYSONEncoder
from trytond.tools import decistmt, grouped_slice
from trytond.tools import decistmt
from trytond.tools import timezone as tz
from trytond.transaction import Transaction, without_check_access
from trytond.wizard import (
@@ -35,14 +35,11 @@ def _find_moves(cls, records):
field = 'product.template'
else:
field = 'product'
for sub_records in grouped_slice(records):
moves = Move.search([
(field, 'in', list(map(int, sub_records))),
],
limit=1, order=[])
if moves:
return moves
return False
moves = Move.search([
(field, 'in', list(map(int, records))),
],
limit=1, order=[])
return bool(moves)
def _check_no_stock(cls, records):
@@ -91,10 +88,9 @@ def _check_no_stock(cls, records):
locations = Location.search([('type', '=', 'storage')])
location_ids = list(map(int, locations))
for company in Company.search([]):
for sub_records in grouped_slice(records):
record2locations = get_record_locations(
company, location_ids, sub_records, grouping)
raise_warning(cls, company, record2locations)
record2locations = get_record_locations(
company, location_ids, records, grouping)
raise_warning(cls, company, record2locations)
class Template(metaclass=PoolMeta):
@@ -279,12 +275,11 @@ class Product(StockMixin, object, metaclass=PoolMeta):
pool = Pool()
Move = pool.get('stock.move')
deactivated = super().deactivate_replaced(products=products)
for sub_products in grouped_slice(deactivated):
moves = Move.search([
('product', 'in', list(map(int, sub_products))),
('state', 'in', ['staging', 'draft']),
])
Move.delete(moves)
moves = Move.search([
('product', 'in', list(map(int, products))),
('state', 'in', ['staging', 'draft']),
])
Move.delete(moves)
return deactivated
@classmethod
@@ -367,16 +362,14 @@ class Product(StockMixin, object, metaclass=PoolMeta):
cost = round_price(cost)
costs[cost].append(product)
updated = []
for sub_products in grouped_slice(products):
domain = [
('unit_price_updated', '=', True),
cls._domain_moves_cost(),
('product', 'in', [p.id for p in sub_products]),
]
if start:
domain.append(('effective_date', '>=', start))
updated += Move.search(domain, order=[])
domain = [
('unit_price_updated', '=', True),
cls._domain_moves_cost(),
('product', 'in', products),
]
if start:
domain.append(('effective_date', '>=', start))
updated = Move.search(domain, order=[])
if updated:
Move.write(updated, {'unit_price_updated': False})
@@ -675,10 +668,6 @@ class ProductQuantitiesByWarehouse(ModelSQL, ModelView):
product = Product.__table__()
from_ = move.join(product, condition=move.product == product.id)
if product_template:
if len(product_template) > transaction.database.IN_MAX:
raise AccessError(gettext(
'stock.msg_product_quantities_max',
max=transaction.database.IN_MAX))
product_clause = fields.SQL_OPERATORS['in'](
product.template, product_template)
else:
@@ -692,10 +681,6 @@ class ProductQuantitiesByWarehouse(ModelSQL, ModelView):
if isinstance(product, int):
product = [product]
if product:
if len(product) > transaction.database.IN_MAX:
raise AccessError(gettext(
'stock.msg_product_quantities_max',
max=transaction.database.IN_MAX))
product_clause = fields.SQL_OPERATORS['in'](
move.product, product)
else:
@@ -943,10 +928,6 @@ class ProductQuantitiesByWarehouseMove(ModelSQL, ModelView):
product = Product.__table__()
from_ = move.join(product, condition=move.product == product.id)
if product_template:
if len(product_template) > transaction.database.IN_MAX:
raise AccessError(gettext(
'stock.msg_product_quantities_max',
max=transaction.database.IN_MAX))
product_clause = fields.SQL_OPERATORS['in'](
product.template, product_template)
else:
@@ -959,10 +940,6 @@ class ProductQuantitiesByWarehouseMove(ModelSQL, ModelView):
if isinstance(product, int):
product = [product]
if product:
if len(product) > transaction.database.IN_MAX:
raise AccessError(gettext(
'stock.msg_product_quantities_max',
max=transaction.database.IN_MAX))
product_clause = fields.SQL_OPERATORS['in'](
move.product, product)
else:
@@ -1139,17 +1116,16 @@ class RecomputeCostPrice(Wizard):
products = []
from_ = None
for sub_products in grouped_slice(products):
moves = Move.search([
('unit_price_updated', '=', True),
Product._domain_moves_cost(),
('product', 'in', [p.id for p in sub_products]),
],
order=[('effective_date', 'ASC')],
limit=1)
if moves:
move, = moves
from_ = min(from_ or datetime.date.max, move.effective_date)
moves = Move.search([
('unit_price_updated', '=', True),
Product._domain_moves_cost(),
('product', 'in', products),
],
order=[('effective_date', 'ASC')],
limit=1)
if moves:
move, = moves
from_ = min(from_ or datetime.date.max, move.effective_date)
return {'from_': from_}
def transition_recompute(self):
+6 -10
View File
@@ -15,7 +15,7 @@ from trytond.model import ModelSQL, ModelView, fields, sum_tree
from trytond.modules.currency.fields import Monetary
from trytond.pool import Pool
from trytond.pyson import Eval, If
from trytond.tools import grouped_slice, pairwise_longest
from trytond.tools import pairwise_longest
from trytond.tools.chart import sparkline
from trytond.transaction import Transaction
@@ -592,15 +592,11 @@ class CategoryTree(ModelSQL, ModelView):
categories = cls.search([
('parent', 'child_of', [c.id for c in categories]),
])
ids = [c.id for c in categories]
reporting_categories = []
for sub_ids in grouped_slice(ids):
sub_ids = list(sub_ids)
where = fields.SQL_OPERATORS['in'](
reporting_category.id, sub_ids)
cursor.execute(
*reporting_category.select(reporting_category.id, where=where))
reporting_categories.extend(r for r, in cursor)
cursor.execute(*reporting_category.select(
reporting_category.id,
where=fields.SQL_OPERATORS['in'](
reporting_category.id, map(int, categories))))
reporting_categories = [r for r, in cursor]
result = {}
reporting_categories = ReportingCategory.browse(reporting_categories)