From ea1393bb840140e00ef9901ad67cca03e58c439c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Tue, 10 Jun 2025 23:22:01 +0200 Subject: [PATCH] Reduce the clauses of child-of using common roots For the path, if the longer path contains another searched path, we can ignore it. For MPTT, if a range is inside a larger range, we can also ignore it. Closes #14064 --- trytond/trytond/model/fields/many2one.py | 29 +++++++++++++++++--- trytond/trytond/tests/test_field_many2one.py | 15 ++++++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/trytond/trytond/model/fields/many2one.py b/trytond/trytond/model/fields/many2one.py index 2fcf6f0411..c881d5cc01 100644 --- a/trytond/trytond/model/fields/many2one.py +++ b/trytond/trytond/model/fields/many2one.py @@ -5,6 +5,7 @@ import warnings from sql import As, Column, Expression, Literal, Null, Query, With from sql.aggregate import Max from sql.conditionals import Coalesce +from sql.functions import CharLength from sql.operators import Or from trytond.config import config @@ -134,10 +135,20 @@ class Many2One(Field): Target = self.get_target() path_column = getattr(Target, self.path).sql_column(table) path_column = Coalesce(path_column, '') - cursor.execute(*table.select(path_column, where=red_sql)) + cursor.execute(*table.select( + path_column, where=red_sql, + order_by=[CharLength(path_column).desc, path_column.asc])) if operator.endswith('child_of'): - where = Or() + paths = set() for path, in cursor: + discard = set() + for opath in paths: + if path in opath: + discard.add(opath) + paths -= discard + paths.add(path) + where = Or() + for path in paths: where.append(path_column.like(path + '%')) else: ids = [int(x) for path, in cursor for x in path.split('/')[:-1]] @@ -156,9 +167,19 @@ class Many2One(Field): Target = self.get_target() left = getattr(Target, self.left).sql_column(table) right = getattr(Target, self.right).sql_column(table) - cursor.execute(*table.select(left, right, where=red_sql)) - where = Or() + cursor.execute(*table.select( + left, right, where=red_sql, + order_by=[(right - left).asc, left.asc])) + ranges = set() for l, r in cursor: + discard = set() + for ol, or_ in ranges: + if l < ol and or_ < r: + discard.add((ol, or_)) + ranges -= discard + ranges.add((l, r)) + where = Or() + for l, r in ranges: if operator.endswith('child_of'): where.append((left >= l) & (right <= r)) else: diff --git a/trytond/trytond/tests/test_field_many2one.py b/trytond/trytond/tests/test_field_many2one.py index b992904cea..8c9c2480d3 100644 --- a/trytond/trytond/tests/test_field_many2one.py +++ b/trytond/trytond/tests/test_field_many2one.py @@ -400,6 +400,21 @@ class FieldMany2OneTreeTestCase(TestCase): self.first1, self.first2, self.first3, self.first4, self.second2, self.second3, self.second4])) + @with_transaction() + def test_search_child_of_first1_second1(self): + "Test search many2one child of first1 and second1" + Many2One = Pool().get(self.model_name) + self.create_tree(Many2One) + + result = Many2One.search([ + ('many2one', 'child_of', + [self.first1.id, self.second1.id]), + ]) + + self.assertListEqual( + sorted(result), + sorted([self.first1, self.second1, self.second2])) + @with_transaction() def test_search_child_of_empty(self): "Test search many2one child of empty"