diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG index 1a7e465630..fdeaff06fd 100644 --- a/trytond/CHANGELOG +++ b/trytond/CHANGELOG @@ -1,3 +1,4 @@ +* Replace target_search by use of count() * Manage domain on id in single value list as unique * Do not validate domain for empty fields * Warn about implicit save() of unsaved relation diff --git a/trytond/doc/ref/fields.rst b/trytond/doc/ref/fields.rst index dc480cef82..a9d0e4673c 100644 --- a/trytond/doc/ref/fields.rst +++ b/trytond/doc/ref/fields.rst @@ -694,7 +694,7 @@ Instance methods: Many2One -------- -.. class:: Many2One(model_name, string[, left[, right[, path[, ondelete[, datetime_field[, target_search[, search_order[, search_context[, \**options]]]]]]]]]) +.. class:: Many2One(model_name, string[, left[, right[, path[, ondelete[, datetime_field[, search_order[, search_context[, \**options]]]]]]]]) A many-to-one relation field that refers to a record of the named model. @@ -769,23 +769,6 @@ Many2One :attr:`~trytond.model.ModelSQL._history` to request a value for a given date and time on a historicized model. -.. attribute:: Many2One.target_search - - Define the kind of SQL query to use when searching on related target. - - Allowed values are: - - - ``subquery``: uses a subquery based on the ids. - - - ``join``: adds a join on the main query. - - ``join`` is the default value. - - .. note:: - - ``join`` could improve the performance if the target has a huge amount of - records. - .. attribute:: Many2One.search_order A :ref:`PYSON ` expression defining the default order used to diff --git a/trytond/doc/topics/configuration.rst b/trytond/doc/topics/configuration.rst index c62126d31d..d350fca6aa 100644 --- a/trytond/doc/topics/configuration.rst +++ b/trytond/doc/topics/configuration.rst @@ -180,6 +180,13 @@ The number of retries when a database operational error occurs during a request. Default: ``5`` +subquery_threshold +~~~~~~~~~~~~~~~~~~ + +The number of records in the target relation under which a sub-query is used. + +Default: ``1000`` + language ~~~~~~~~ diff --git a/trytond/trytond/config.py b/trytond/trytond/config.py index b44032fe3f..bbf13e41f9 100644 --- a/trytond/trytond/config.py +++ b/trytond/trytond/config.py @@ -64,6 +64,7 @@ class TrytonConfigParser(configparser.ConfigParser): self.set('database', 'retry', '5') self.set('database', 'language', 'en') self.set('database', 'timeout', str(30 * 60)) + self.set('database', 'subquery_threshold', str(1_000)) self.add_section('request') self.set('request', 'max_size', str(2 * 1024 * 1024)) self.set('request', 'max_size_authenticated', diff --git a/trytond/trytond/model/fields/many2one.py b/trytond/trytond/model/fields/many2one.py index 5c290b1438..39dea38174 100644 --- a/trytond/trytond/model/fields/many2one.py +++ b/trytond/trytond/model/fields/many2one.py @@ -5,6 +5,7 @@ from sql.aggregate import Max from sql.conditionals import Coalesce from sql.operators import Or +from trytond.config import config from trytond.pool import Pool from trytond.pyson import PYSONEncoder from trytond.tools import cached_property, reduce_ids @@ -14,6 +15,8 @@ from .field import ( Field, context_validate, instantiate_context, search_order_validate, with_inactive_records) +_subquery_threshold = config.getint('database', 'subquery_threshold') + class Many2One(Field): ''' @@ -23,7 +26,7 @@ class Many2One(Field): _sql_type = 'INTEGER' def __init__(self, model_name, string='', left=None, right=None, path=None, - ondelete='SET NULL', datetime_field=None, target_search='join', + ondelete='SET NULL', datetime_field=None, search_order=None, search_context=None, help='', required=False, readonly=False, domain=None, states=None, on_change=None, on_change_with=None, depends=None, context=None, @@ -57,8 +60,6 @@ class Many2One(Field): self.right = right self.path = path self.datetime_field = datetime_field - assert target_search in ['subquery', 'join'] - self.target_search = target_search self.__search_order = None self.search_order = search_order self.__search_context = None @@ -258,7 +259,7 @@ class Many2One(Field): else: _, target_name = name.split('.', 1) target_domain = [(target_name,) + tuple(domain[1:])] - if self.target_search == 'subquery': + if Target.count() < _subquery_threshold: query = Target.search(target_domain, order=[], query=True) return column.in_(query) else: diff --git a/trytond/trytond/tests/test_field_many2one.py b/trytond/trytond/tests/test_field_many2one.py index 47cf3b0216..b2ae9745ee 100644 --- a/trytond/trytond/tests/test_field_many2one.py +++ b/trytond/trytond/tests/test_field_many2one.py @@ -2,6 +2,8 @@ # this repository contains the full copyright notices and license terms. import unittest +from sql import Join + from trytond.model.exceptions import DomainValidationError from trytond.pool import Pool from trytond.tests.test_tryton import activate_module, with_transaction @@ -157,7 +159,9 @@ class FieldMany2OneTestCase(unittest.TestCase): self.assertListEqual(result, [record]) @with_transaction() - def _test_search_join(self, target_search): + 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') @@ -170,24 +174,28 @@ class FieldMany2OneTestCase(unittest.TestCase): {'many2one': target2}, ]) - def set_target_search(target_search): - Many2One.many2one.target_search = target_search - self.addCleanup(set_target_search, Many2One.many2one.target_search) - set_target_search(target_search) + 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" - self._test_search_join('join') + query = self._test_search_join(0) + self.assertIsInstance(query.from_[0], Join) def test_search_subquery(self): "Test search by many2one subquery" - self._test_search_join('subquery') + query = self._test_search_join() + self.assertNotIsInstance(query.from_[0], Join) @with_transaction() def test_context_attribute(self):