Use the EXISTS operator when using the 'where' operator in Many2One
Closes #11935
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
* Use the EXISTS operator when using the 'where' operator in Many2One
|
||||
* Redact sensitive values from the log (issue14220)
|
||||
* Add multiple attribute to the buttons in the tree view
|
||||
* Manage readonly wizard
|
||||
|
||||
@@ -8,7 +8,7 @@ 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 sql.operators import Exists, Or
|
||||
|
||||
from trytond.config import config
|
||||
from trytond.pool import Pool
|
||||
@@ -225,6 +225,7 @@ class Many2One(Field):
|
||||
pool = Pool()
|
||||
Rule = pool.get('ir.rule')
|
||||
Target = self.get_target()
|
||||
use_subquery = Target.estimated_count() < _subquery_threshold
|
||||
|
||||
table, _ = tables[None]
|
||||
name, operator, value = domain[:3]
|
||||
@@ -278,8 +279,11 @@ class Many2One(Field):
|
||||
target_id, = query.columns
|
||||
if isinstance(target_id, As):
|
||||
target_id = target_id.expression
|
||||
query.where &= target_id == column
|
||||
expression = column.in_(query)
|
||||
if use_subquery:
|
||||
expression = column.in_(query)
|
||||
else:
|
||||
query.where &= target_id == column
|
||||
expression = Exists(query)
|
||||
if operator.startswith('not'):
|
||||
return ~expression
|
||||
return expression
|
||||
@@ -299,7 +303,7 @@ class Many2One(Field):
|
||||
# No need to join with the target table
|
||||
return super().convert_domain(
|
||||
(self.name, operator, value), tables, Model)
|
||||
elif Target.estimated_count() < _subquery_threshold:
|
||||
elif use_subquery:
|
||||
query = Target.search(target_domain, order=[], query=True)
|
||||
expression = column.in_(query)
|
||||
else:
|
||||
|
||||
@@ -3,21 +3,16 @@
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from sql import Join
|
||||
from sql import Join, Table
|
||||
|
||||
from trytond.config import config
|
||||
from trytond.model.exceptions import DomainValidationError
|
||||
from trytond.pool import Pool
|
||||
from trytond.tests.test_tryton import (
|
||||
TestCase, activate_module, with_transaction)
|
||||
|
||||
|
||||
class FieldMany2OneTestCase(TestCase):
|
||||
"Test Field Many2One"
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
activate_module('tests')
|
||||
class CommonTestCaseMixin:
|
||||
|
||||
@with_transaction()
|
||||
def test_create_id(self):
|
||||
@@ -104,6 +99,50 @@ class FieldMany2OneTestCase(TestCase):
|
||||
|
||||
self.assertEqual(many2one.many2one, target)
|
||||
|
||||
@with_transaction()
|
||||
def test_context_attribute(self):
|
||||
"Test context on many2one attribute"
|
||||
pool = Pool()
|
||||
Many2One = pool.get('test.many2one_context')
|
||||
Target = pool.get('test.many2one_context.target')
|
||||
|
||||
target, = Target.create([{}])
|
||||
record, = Many2One.create([{
|
||||
'target': target.id,
|
||||
}])
|
||||
|
||||
self.assertEqual(record.target.context, 'foo')
|
||||
|
||||
@with_transaction()
|
||||
def test_context_read(self):
|
||||
"Test context on many2one read"
|
||||
pool = Pool()
|
||||
Many2One = pool.get('test.many2one_context')
|
||||
Target = pool.get('test.many2one_context.target')
|
||||
|
||||
target, = Target.create([{}])
|
||||
record, = Many2One.create([{
|
||||
'target': target.id,
|
||||
}])
|
||||
data, = Many2One.read([record.id], ['target.context'])
|
||||
|
||||
self.assertEqual(data['target.']['context'], 'foo')
|
||||
|
||||
@with_transaction()
|
||||
def test_context_set(self):
|
||||
"Test context on many2one set"
|
||||
pool = Pool()
|
||||
Many2One = pool.get('test.many2one_context')
|
||||
Target = pool.get('test.many2one_context.target')
|
||||
|
||||
target, = Target.create([{}])
|
||||
record = Many2One(target=target.id)
|
||||
|
||||
self.assertEqual(record.target.context, 'foo')
|
||||
|
||||
|
||||
class SearchTestCaseMixin:
|
||||
|
||||
@with_transaction()
|
||||
def test_search_order_default(self):
|
||||
"Test search order by many2one default"
|
||||
@@ -165,45 +204,6 @@ class FieldMany2OneTestCase(TestCase):
|
||||
result = Many2One.search([('many2one', '=', target.id)])
|
||||
self.assertListEqual(result, [record])
|
||||
|
||||
@with_transaction()
|
||||
def _test_search_join(self, subquery_threshold=1_000):
|
||||
from trytond.model.fields import many2one
|
||||
|
||||
pool = Pool()
|
||||
Target = pool.get('test.many2one_target')
|
||||
Many2One = pool.get('test.many2one')
|
||||
target1, target2 = Target.create([
|
||||
{'value': 1},
|
||||
{'value': 2},
|
||||
])
|
||||
many2one1, many2one2 = Many2One.create([
|
||||
{'many2one': target1},
|
||||
{'many2one': target2},
|
||||
])
|
||||
|
||||
previous = many2one._subquery_threshold
|
||||
many2one._subquery_threshold = subquery_threshold
|
||||
self.addCleanup(setattr, many2one, '_subquery_threshold', previous)
|
||||
|
||||
many2ones = Many2One.search([
|
||||
('many2one.value', '=', 1),
|
||||
])
|
||||
self.assertListEqual(many2ones, [many2one1])
|
||||
|
||||
return Many2One.search([
|
||||
('many2one.value', '=', 1),
|
||||
], query=True)
|
||||
|
||||
def test_search_join(self):
|
||||
"Test search by many2one join"
|
||||
query = self._test_search_join(0)
|
||||
self.assertIsInstance(query.from_[0], Join)
|
||||
|
||||
def test_search_subquery(self):
|
||||
"Test search by many2one subquery"
|
||||
query = self._test_search_join()
|
||||
self.assertNotIsInstance(query.from_[0], Join)
|
||||
|
||||
@with_transaction()
|
||||
def test_search_nested_null(self):
|
||||
"Test search by null many2one"
|
||||
@@ -278,45 +278,88 @@ class FieldMany2OneTestCase(TestCase):
|
||||
self.assertEqual(record.many2one.value, 42)
|
||||
|
||||
@with_transaction()
|
||||
def test_context_attribute(self):
|
||||
"Test context on many2one attribute"
|
||||
def test_search_strategy(self):
|
||||
"Test search many2one with the right strategy"
|
||||
pool = Pool()
|
||||
Many2One = pool.get('test.many2one_context')
|
||||
Target = pool.get('test.many2one_context.target')
|
||||
Target = pool.get('test.many2one_target')
|
||||
Many2One = pool.get('test.many2one')
|
||||
target1, target2 = Target.create([
|
||||
{'value': 1},
|
||||
{'value': 2},
|
||||
])
|
||||
many2one1, many2one2 = Many2One.create([
|
||||
{'many2one': target1},
|
||||
{'many2one': target2},
|
||||
])
|
||||
|
||||
target, = Target.create([{}])
|
||||
record, = Many2One.create([{
|
||||
'target': target.id,
|
||||
}])
|
||||
many2ones = Many2One.search([
|
||||
('many2one.value', '=', 1),
|
||||
])
|
||||
self.assertListEqual(many2ones, [many2one1])
|
||||
|
||||
self.assertEqual(record.target.context, 'foo')
|
||||
query = Many2One.search([
|
||||
('many2one.value', '=', 1),
|
||||
], query=True)
|
||||
|
||||
sql_statement = {
|
||||
'IN': Table,
|
||||
'EXISTS': Join,
|
||||
}[self._strategy]
|
||||
self.assertIsInstance(query.from_[0], sql_statement)
|
||||
|
||||
@with_transaction()
|
||||
def test_context_read(self):
|
||||
"Test context on many2one read"
|
||||
def test_search_where_strategy(self):
|
||||
"Test search on many2one using where uses the right strategy"
|
||||
pool = Pool()
|
||||
Many2One = pool.get('test.many2one_context')
|
||||
Target = pool.get('test.many2one_context.target')
|
||||
Target = pool.get('test.many2one_target')
|
||||
Many2One = pool.get('test.many2one')
|
||||
target1, target2 = Target.create([
|
||||
{'value': 1},
|
||||
{'value': 2},
|
||||
])
|
||||
many2one1, many2one2 = Many2One.create([
|
||||
{'many2one': target1},
|
||||
{'many2one': target2},
|
||||
])
|
||||
|
||||
target, = Target.create([{}])
|
||||
record, = Many2One.create([{
|
||||
'target': target.id,
|
||||
}])
|
||||
data, = Many2One.read([record.id], ['target.context'])
|
||||
many2ones = Many2One.search([
|
||||
('many2one', 'where', [('value', '=', 1)]),
|
||||
])
|
||||
self.assertListEqual(many2ones, [many2one1])
|
||||
query = Many2One.search([
|
||||
('many2one', 'where', [('value', '=', 1)]),
|
||||
], query=True)
|
||||
self.assertIn(self._strategy, str(query))
|
||||
|
||||
self.assertEqual(data['target.']['context'], 'foo')
|
||||
|
||||
@with_transaction()
|
||||
def test_context_set(self):
|
||||
"Test context on many2one set"
|
||||
pool = Pool()
|
||||
Many2One = pool.get('test.many2one_context')
|
||||
Target = pool.get('test.many2one_context.target')
|
||||
class FieldMany2OneTestCase(
|
||||
TestCase, CommonTestCaseMixin, SearchTestCaseMixin):
|
||||
"Test Field Many2One"
|
||||
_strategy = 'IN'
|
||||
|
||||
target, = Target.create([{}])
|
||||
record = Many2One(target=target.id)
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
activate_module('tests')
|
||||
|
||||
self.assertEqual(record.target.context, 'foo')
|
||||
|
||||
class FieldMany2OneExistsTestCase(
|
||||
TestCase, CommonTestCaseMixin, SearchTestCaseMixin):
|
||||
"Test Field Many2One"
|
||||
_strategy = 'EXISTS'
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
activate_module('tests')
|
||||
|
||||
def setUp(self):
|
||||
from trytond.model.fields import many2one
|
||||
super().setUp()
|
||||
previous = int(
|
||||
config.get('database', 'subquery_threshold', default='1_000'))
|
||||
many2one._subquery_threshold = 0
|
||||
self.addCleanup(setattr, many2one, '_subquery_threshold', previous)
|
||||
|
||||
|
||||
class FieldMany2OneTreeTestCase(TestCase):
|
||||
|
||||
Reference in New Issue
Block a user