changeset 87650e5c757e in trytond:default
details: https://hg.tryton.org/trytond?cmd=changeset&node=87650e5c757e
description:
        Deduplicate entries in domain inversion's simplify

        issue11248
        review397301002
diffstat:

 CHANGELOG                         |   1 +
 trytond/tests/test_tools.py       |  55 ++++++++++++++++++++++++++++--
 trytond/tools/domain_inversion.py |  71 +++++++++++++++++++++++++++++++-------
 3 files changed, 109 insertions(+), 18 deletions(-)

diffs (185 lines):

diff -r 30f980bb3226 -r 87650e5c757e CHANGELOG
--- a/CHANGELOG Sun Mar 27 16:06:09 2022 +0200
+++ b/CHANGELOG Wed Mar 30 19:20:31 2022 +0200
@@ -1,3 +1,4 @@
+* Deduplicate entries in domain inversion's simplify
 * Prevent to create and delete singleton
 * Allow CORS on root path
 * Allow button access to be deactivated
diff -r 30f980bb3226 -r 87650e5c757e trytond/tests/test_tools.py
--- a/trytond/tests/test_tools.py       Sun Mar 27 16:06:09 2022 +0200
+++ b/trytond/tests/test_tools.py       Wed Mar 30 19:20:31 2022 +0200
@@ -533,7 +533,7 @@
             domain_inversion(domain, 'x', {'y': 7, 'z': 'abc'}), True)
         self.assertEqual(
             domain_inversion(domain, 'x', {'y': 4, 'z': 'b'}),
-            ['OR', [['x', '=', 3]], [['x', '=', 2]]])
+            ['OR', ['x', '=', 3], ['x', '=', 2]])
 
     def test_parse(self):
         domain = parse([['x', '=', 5]])
@@ -555,16 +555,34 @@
         domain = [[['x', '=', 3]]]
         self.assertEqual(simplify(domain), [['x', '=', 3]])
 
+        domain = [[['x', '=', 3], ['y', '=', 4]]]
+        self.assertEqual(simplify(domain), [['x', '=', 3], ['y', '=', 4]])
+
         domain = ['OR', ['x', '=', 3]]
         self.assertEqual(simplify(domain), [['x', '=', 3]])
 
         domain = ['OR', [['x', '=', 3]], [['y', '=', 5]]]
         self.assertEqual(
-            simplify(domain), ['OR', [['x', '=', 3]], [['y', '=', 5]]])
+            simplify(domain), ['OR', ['x', '=', 3], ['y', '=', 5]])
 
         domain = ['OR', ['x', '=', 3], ['AND', ['y', '=', 5]]]
         self.assertEqual(
-            simplify(domain), ['OR', ['x', '=', 3], [['y', '=', 5]]])
+            simplify(domain), ['OR', ['x', '=', 3], ['y', '=', 5]])
+
+        domain = [['x', '=', 3], ['OR']]
+        self.assertEqual(simplify(domain), [['x', '=', 3]])
+
+        domain = ['OR', ['x', '=', 3], []]
+        self.assertEqual(simplify(domain), [])
+
+        domain = ['OR', ['x', '=', 3], ['OR']]
+        self.assertEqual(simplify(domain), [])
+
+        domain = [['x', '=', 3], []]
+        self.assertEqual(simplify(domain), [['x', '=', 3]])
+
+        domain = [['x', '=', 3], ['AND']]
+        self.assertEqual(simplify(domain), [['x', '=', 3]])
 
         domain = ['AND']
         self.assertEqual(simplify(domain), [])
@@ -572,6 +590,35 @@
         domain = ['OR']
         self.assertEqual(simplify(domain), [])
 
+    def test_simplify_deduplicate(self):
+        "Test deduplicate"
+        clause = ('x', '=', 'x')
+        another = ('y', '=', 'y')
+        third = ('z', '=', 'z')
+        tests = [
+            ([], []),
+            (['OR', []], []),
+            (['AND', []], []),
+            ([clause], [clause]),
+            (['OR', clause], [clause]),
+            ([clause, clause], [clause]),
+            (['OR', clause, clause], [clause]),
+            ([clause, [clause, clause]], [clause]),
+            ([clause, another], [clause, another]),
+            (['OR', clause, another], ['OR', clause, another]),
+            ([clause, clause, another], [clause, another]),
+            ([clause, [clause, clause], another], [clause, another]),
+            ([clause, clause, another, another], [clause, another]),
+            ([clause, another, clause, another], [clause, another]),
+            (
+                ['AND', ['OR', clause, another], third],
+                ['AND', ['OR', clause, another], third]),
+            ]
+
+        for input, expected in tests:
+            with self.subTest(input=input):
+                self.assertEqual(simplify(input), expected)
+
     def test_merge(self):
         domain = [['x', '=', 6], ['y', '=', 7]]
         self.assertEqual(merge(domain), ['AND', ['x', '=', 6], ['y', '=', 7]])
@@ -621,7 +668,7 @@
         self.assertEqual(concat([], []), [])
         self.assertEqual(
             concat(domain1, domain2, domoperator='OR'),
-            ['OR', [['a', '=', 1]], [['b', '=', 2]]])
+            ['OR', ['a', '=', 1], ['b', '=', 2]])
 
     def test_unique_value(self):
         domain = [['a', '=', 1]]
diff -r 30f980bb3226 -r 87650e5c757e trytond/tools/domain_inversion.py
--- a/trytond/tools/domain_inversion.py Sun Mar 27 16:06:09 2022 +0200
+++ b/trytond/tools/domain_inversion.py Wed Mar 30 19:20:31 2022 +0200
@@ -284,22 +284,65 @@
             for part in domain]
 
 
-def simplify(domain):
-    "remove unused domain delimiter"
-    if is_leaf(domain):
-        return domain
-    elif domain in ('OR', 'AND'):
-        return domain
-    elif domain in (['OR'], ['AND']):
+def bool_operator(domain):
+    "Returns the boolean operator used by a domain"
+    bool_op = 'AND'
+    if domain and domain[0] in ['AND', 'OR']:
+        bool_op = domain[0]
+    return bool_op
+
+
+def simplify_nested(domain):
+    """Simplify extra domain markers"""
+    if not domain:
         return []
-    elif (isinstance(domain, list) and len(domain) == 1
-            and not is_leaf(domain[0])):
-        return simplify(domain[0])
-    elif (isinstance(domain, list) and len(domain) == 2
-            and domain[0] in ('AND', 'OR')):
-        return [simplify(domain[1])]
+    elif is_leaf(domain):
+        return [domain]
+    elif domain in ['OR', 'AND']:
+        return [domain]
+    elif isinstance(domain, list) and len(domain) == 1:
+        return simplify_nested(domain[0])
     else:
-        return [simplify(branch) for branch in domain]
+        simplified = []
+        domain_op = bool_operator(domain)
+        for branch in domain:
+            simplified_branch = simplify_nested(branch)
+            if (bool_operator(branch) == domain_op
+                    or len(simplified_branch) == 1):
+                simplified.extend(simplified_branch)
+            else:
+                simplified.append(simplified_branch)
+        return simplified
+
+
+def simplify_duplicate(domain):
+    """Remove duplicates subdomain from domain"""
+    dedup_branches = []
+    bool_op = None
+    if domain[0] in ['AND', 'OR']:
+        bool_op, *domain = domain
+    for branch in domain:
+        simplified_branch = simplify(branch)
+        if not simplified_branch:
+            if bool_op == 'OR':
+                return []
+            else:
+                continue
+        elif simplified_branch not in dedup_branches:
+            dedup_branches.append(simplified_branch)
+    if bool_op and len(dedup_branches) > 1:
+        dedup_branches.insert(0, bool_op)
+    return dedup_branches
+
+
+def simplify(domain):
+    """Remove duplicate expressions and useless OR/AND"""
+    if is_leaf(domain):
+        return [domain]
+    elif not domain:
+        return domain
+    else:
+        return simplify_nested(simplify_duplicate(domain))
 
 
 def merge(domain, domoperator=None):

Reply via email to