[PERF] product: Improve name_search performance

Issue:
The name_search performance was highly inefficient with a high number of
records.

Analyze:
The bottleneck came from the NOT IN search on the product_template.

Fix:
As a NOT IN  is not efficient, an not exists must be used.

Note:
The ORM has to be bypassed has no "not exists" feature has been
implemented yet.

Benchmark:
| # Input data | Before PR | After PR |
|:-------------:|:----------:|:---------:|
| 182386                    |     15+ min           |   ~100 ms |
|       29            |      ~29 ms          |    ~28 ms           |

Related task:
opw-3554133

closes odoo/odoo#145095

X-original-commit: 955d28a485d569569af2792fa014b52c1829d178
Signed-off-by: Benjamin Hanquin (beha) <beha@odoo.com>
This commit is contained in:
Benjamin Hanquin (beha)
2023-12-06 08:20:13 +00:00
parent 18fbdea689
commit 493cfa41aa
2 changed files with 41 additions and 8 deletions
+16 -8
View File
@@ -546,14 +546,22 @@ class ProductTemplate(models.Model):
searched_ids = set(templates.ids)
# some product.templates do not have product.products yet (dynamic variants configuration),
# we need to add the base _name_search to the results
# FIXME awa: this is really not performant at all but after discussing with the team
# we don't see another way to do it
tmpl_without_variant = self.browse()
if not limit or len(searched_ids) < limit:
tmpl_with_variant_ids = self._search([('product_variant_ids.active', '=', True)])
tmpl_without_variant = self.search([('id', 'not in', tmpl_with_variant_ids)])
if tmpl_without_variant:
domain2 = expression.AND([domain, [('id', 'in', tmpl_without_variant.ids)]])
tmpl_without_variant_ids = []
# Useless if variants is not set up as no tmpl_without_variant_ids could exist.
if self.env.user.has_group('product.group_product_variant') and (not limit or len(searched_ids) < limit):
# The ORM has to be bypassed because it would require a NOT IN which is inefficient.
self.env['product.product'].flush_model(['product_tmpl_id', 'active'])
tmpl_without_variant_ids = self.env['product.template']._search([], order='id')
tmpl_without_variant_ids.add_where("""
NOT EXISTS (
SELECT product_tmpl_id
FROM product_product
WHERE product_product.active = true
AND product_template.id = product_product.product_tmpl_id
)
""")
if tmpl_without_variant_ids:
domain2 = expression.AND([domain, [('id', 'in', tmpl_without_variant_ids)]])
searched_ids |= set(super()._name_search(name, domain2, operator, limit, order))
# re-apply product.template order + display_name
+25
View File
@@ -29,3 +29,28 @@ class TestName(TransactionCase):
res = self.env['product.template'].name_search(name='PTN', operator='not ilike')
res_ids = [r[0] for r in res]
self.assertNotIn(self.product.id, res_ids)
def test_product_template_search_name_no_product_product(self):
color_attr = self.env['product.attribute'].create({'name': 'Color', 'create_variant': 'dynamic'})
color_attr_value_r = self.env['product.attribute.value'].create({'name': 'Red', 'attribute_id': color_attr.id})
color_attr_value_b = self.env['product.attribute.value'].create({'name': 'Blue', 'attribute_id': color_attr.id})
template_dyn = self.env['product.template'].create({
'name': 'Test Dynamical',
'attribute_line_ids': [(0, 0, {
'attribute_id': color_attr.id,
'value_ids': [(4, color_attr_value_r.id), (4, color_attr_value_b.id)],
})]
})
product = self.env['product.product'].create({
'name': 'Dynamo Lamp',
'default_code': 'Dynamo',
})
self.assertTrue(template_dyn.has_dynamic_attributes())
# Ensure that template_dyn hasn't any product_product
self.assertEqual(len(template_dyn.product_variant_ids), 0)
# Ensure that Dynam search return Dynamo and Test Dynamical as this
# last have no product_product
res = self.env['product.template'].name_search(name='Dynam', operator='ilike')
res_ids = [r[0] for r in res]
self.assertIn(template_dyn.id, res_ids)
self.assertIn(product.product_tmpl_id.id, res_ids)