From 493cfa41aa140fe740f94559494e7db43dbf77cc Mon Sep 17 00:00:00 2001 From: "Benjamin Hanquin (beha)" Date: Tue, 21 Nov 2023 10:43:16 +0000 Subject: [PATCH] [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) --- addons/product/models/product_template.py | 24 ++++++++++++++-------- addons/product/tests/test_name.py | 25 +++++++++++++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/addons/product/models/product_template.py b/addons/product/models/product_template.py index 7017e17f177..ac10f1ee083 100644 --- a/addons/product/models/product_template.py +++ b/addons/product/models/product_template.py @@ -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 diff --git a/addons/product/tests/test_name.py b/addons/product/tests/test_name.py index d2f643df036..d168118383c 100644 --- a/addons/product/tests/test_name.py +++ b/addons/product/tests/test_name.py @@ -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)