Files
odoo_source/addons/web/tests/test_res_users.py
T
Victor Piryns (pivi) 9117487844 [PERF] web: skip uid first in _name_search on res.users comodel
Description:
When searching with a domain that contains a relational field whos
comodel is `res.users`, with a *pathological* domain of `not ilike`
`'some_string'`, the ORM will call a `_name_search` on `res.users`
with no limit to resolve the leaf when calling `_where_calc`.
The current implementation in the `web` module overrides the
`_name_search` to implement a spec to propose the current user as a
first suggestion, but to do that it first execute the query
(the list conversion), and then manipulates the list of ids to insert
the current user first. (1c2ce8c213)

On large databases with many `res.users`, where the condition matches all
users besides 1, this is a probably Seq.Scan on the `res_users`
table. Then this gigantic list of `ids` will be injected by the ORM
into the main query to satisfy the original domain. This incurs not
only bandwidth costs, but also usually leads to bad plans, ending up
most likely into a Seq.Scan on the original table.
The worse of it, in the case of a `web_search_read`, there is a
`search_count`, so this whole fiasco is repeated once more.
The nail in the coffin, is that the result isn't even needed, when
resolving a comodel's `_name_search`, we care about the subset, the
internal order is irrelevant.

Solution:
The ORM calls the `_name_search` without a limit, while in general the
`name_search` is called with a limit from the front-end, therefor we
can use it as a discriminant -> If no limit, don't suggest `uid` first.

Affected versions:
saas-16.3 -> master (saas-17.2)

Reference:
task-3610657

closes odoo/odoo#154149

X-original-commit: 83aa46a4ab88c0226b1aa1dc36671d3208a0835a
Signed-off-by: Piryns Victor (pivi) <pivi@odoo.com>
2024-02-15 18:21:30 +00:00

40 lines
2.3 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import TransactionCase
class TestResUsers(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.users = cls.env["res.users"].create([
{'name': 'Jean', 'login': 'jean@mail.com', 'password': 'jean@mail.com'},
{'name': 'Jean-Paul', 'login': 'jean-paul@mail.com', 'password': 'jean-paul@mail.com'},
{'name': 'Jean-Jacques', 'login': 'jean-jacques@mail.com', 'password': 'jean-jacques@mail.com'},
{'name': 'Georges', 'login': 'georges@mail.com', 'password': 'georges@mail.com'},
{'name': 'Claude', 'login': 'claude@mail.com', 'password': 'claude@mail.com'},
{'name': 'Pascal', 'login': 'pascal@mail.com', 'password': 'pascal@mail.com'},
])
def test_name_search(self):
"""
Test name search with self assign feature
The self assign feature is present only when a limit is present,
which is the case with the public name_search by default
"""
ResUsers = self.env['res.users']
jean = self.users[0]
user_ids = [id_ for id_, __ in ResUsers.with_user(jean).name_search('')]
self.assertEqual(jean.id, user_ids[0], "The current user, Jean, should be the first in the result.")
user_ids = [id_ for id_, __ in ResUsers.with_user(jean).name_search('Claude')]
self.assertNotIn(jean.id, user_ids, "The current user, Jean, should not be in the result because his name does not fit the condition.")
pascal = self.users[-1]
user_ids = [id_ for id_, __ in ResUsers.with_user(pascal).name_search('')]
self.assertEqual(pascal.id, user_ids[0], "The current user, Pascal, should be the first in the result.")
user_ids = [id_ for id_, __ in ResUsers.with_user(pascal).name_search('', limit=3)]
self.assertEqual(pascal.id, user_ids[0], "The current user, Pascal, should be the first in the result.")
self.assertEqual(len(user_ids), 3, "The number of results found should still respect the limit set.")
jean_paul = self.users[1]
user_ids = [id_ for id_, __ in ResUsers.with_user(jean_paul).name_search('Jean')]
self.assertEqual(jean_paul.id, user_ids[0], "The current user, Jean-Paul, should be the first in the result")