From 54d75a28cd63c4712adb9d5200dbda901d120d7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Voet=20=28ryv=29?= Date: Tue, 30 Aug 2022 11:31:18 +0000 Subject: [PATCH] [IMP] test_new_api: add performance test for `modified`. Part-of: odoo/odoo#99274 --- .../test_new_api/models/test_new_api.py | 39 ++++++ .../test_new_api/security/ir.model.access.csv | 2 + .../test_new_api/tests/test_new_fields.py | 116 ++++++++++++++++++ 3 files changed, 157 insertions(+) diff --git a/odoo/addons/test_new_api/models/test_new_api.py b/odoo/addons/test_new_api/models/test_new_api.py index 3185b6723b3..2458df5bc20 100644 --- a/odoo/addons/test_new_api/models/test_new_api.py +++ b/odoo/addons/test_new_api/models/test_new_api.py @@ -1658,3 +1658,42 @@ class Prefetch(models.Model): ron = fields.Float('Ron Weasley', prefetch='Harry Potter') hansel = fields.Integer('Hansel', prefetch="Hansel and Gretel") gretel = fields.Char('Gretel', prefetch="Hansel and Gretel") + + +class Modified(models.Model): + _name = 'test_new_api.modified' + _description = 'A model to check modified trigger' + + name = fields.Char('Name') + line_ids = fields.One2many('test_new_api.modified.line', 'modified_id') + total_quantity = fields.Integer(compute='_compute_total_quantity') + + @api.depends('line_ids.quantity') + def _compute_total_quantity(self): + for rec in self: + rec.total_quantity = sum(rec.line_ids.mapped('quantity')) + + +class ModifiedLine(models.Model): + _name = 'test_new_api.modified.line' + _description = 'A model to check modified trigger' + + modified_id = fields.Many2one('test_new_api.modified') + modified_name = fields.Char(related="modified_id.name") + quantity = fields.Integer() + price = fields.Float() + total_price = fields.Float(compute='_compute_total_quantity', recursive=True) + total_price_quantity = fields.Float(compute='_compute_total_price_quantity') + + parent_id = fields.Many2one('test_new_api.modified.line') + child_ids = fields.One2many('test_new_api.modified.line', 'parent_id') + + @api.depends('price', 'child_ids.total_price', 'child_ids.price') + def _compute_total_quantity(self): + for rec in self: + rec.total_price = sum(rec.child_ids.mapped('total_price')) + rec.price + + @api.depends('total_price', 'quantity') + def _compute_total_price_quantity(self): + for rec in self: + rec.total_price_quantity = rec.total_price * rec.quantity diff --git a/odoo/addons/test_new_api/security/ir.model.access.csv b/odoo/addons/test_new_api/security/ir.model.access.csv index 0b4bd42086c..961e623492c 100644 --- a/odoo/addons/test_new_api/security/ir.model.access.csv +++ b/odoo/addons/test_new_api/security/ir.model.access.csv @@ -94,3 +94,5 @@ access_test_new_api_precompute_readonly,access_test_new_api_precompute_readonly, access_test_new_api_precompute_required,access_test_new_api_precompute_required,model_test_new_api_precompute_required,,1,0,0,0 access_test_new_api_precompute_monetary,access_test_new_api_precompute_monetary,model_test_new_api_precompute_monetary,,1,0,0,0 access_test_new_api_prefetch,access_test_new_api_prefetch,model_test_new_api_prefetch,,1,0,0,0 +access_test_new_api_modified,access_test_new_api_modified,model_test_new_api_modified,base.group_user,1,1,1,1 +access_test_new_api_modified_line,access_test_new_api_modified_line,model_test_new_api_modified_line,base.group_user,1,1,1,1 diff --git a/odoo/addons/test_new_api/tests/test_new_fields.py b/odoo/addons/test_new_api/tests/test_new_fields.py index 05f78fadb49..7e64d8c0d99 100644 --- a/odoo/addons/test_new_api/tests/test_new_fields.py +++ b/odoo/addons/test_new_api/tests/test_new_fields.py @@ -4085,3 +4085,119 @@ class TestPrecompute(common.TransactionCase): ] with self.assertQueries(QUERIES): model.create({}) + + +class TestModifiedPerformance(common.TransactionCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.Modified = cls.env['test_new_api.modified'] + cls.ModifiedLine = cls.env['test_new_api.modified.line'] + cls.modified_a = cls.Modified.create({ + 'name': 'Test', + }) + cls.modified_line_a = cls.ModifiedLine.create({ + 'modified_id': cls.modified_a.id, + 'quantity': 5, + 'price': 1, + }) + cls.modified_line_a_child = cls.ModifiedLine.create({ + 'modified_id': cls.modified_a.id, + 'quantity': 5, + 'price': 2, + 'parent_id': cls.modified_line_a.id, + }) + cls.modified_line_a_child_child = cls.ModifiedLine.create({ + 'modified_id': cls.modified_a.id, + 'quantity': 5, + 'price': 3, + 'parent_id': cls.modified_line_a_child.id, + }) + cls.env.invalidate_all() # Clean the cache + + def test_modified_trigger_related(self): + with self.assertQueries([""" + SELECT "test_new_api_modified_line".id + FROM "test_new_api_modified_line" + WHERE ("test_new_api_modified_line"."modified_id" IN %s) + ORDER BY "test_new_api_modified_line"."id" + """, + """ + SELECT "test_new_api_modified_line"."id" AS "id", "test_new_api_modified_line"."modified_id" AS "modified_id" + FROM "test_new_api_modified_line" + WHERE "test_new_api_modified_line".id IN %s + """], flush=False): + # Two queries to fetch the `line_ids` in order to invalidate the cache for `modified_name` + # (even if the cache is empty for this field) + self.modified_a.name = "Other" + + self.assertEqual(self.modified_line_a.modified_name, 'Other') # check + + def test_modified_trigger_no_store_compute(self): + with self.assertQueries([""" + SELECT "test_new_api_modified_line"."id" AS "id", "test_new_api_modified_line"."modified_id" AS "modified_id", + "test_new_api_modified_line"."quantity" AS "quantity", "test_new_api_modified_line"."price" AS "price", + "test_new_api_modified_line"."parent_id" AS "parent_id", "test_new_api_modified_line"."create_uid" AS "create_uid", + "test_new_api_modified_line"."create_date" AS "create_date", "test_new_api_modified_line"."write_uid" AS "write_uid", + "test_new_api_modified_line"."write_date" AS "write_date" + FROM "test_new_api_modified_line" + WHERE "test_new_api_modified_line".id IN %s + """], flush=False): + # One query to fetch `modified_id` in order to invalidate `total_quantity` + self.modified_line_a.quantity = 8 + + self.assertEqual(self.modified_a.total_quantity, 18) + + def test_modified_trigger_recursive_empty_cache(self): + with self.assertQueries([""" + SELECT "test_new_api_modified_line"."id" AS "id", "test_new_api_modified_line"."modified_id" AS "modified_id", + "test_new_api_modified_line"."quantity" AS "quantity", "test_new_api_modified_line"."price" AS "price", + "test_new_api_modified_line"."parent_id" AS "parent_id", "test_new_api_modified_line"."create_uid" AS "create_uid", + "test_new_api_modified_line"."create_date" AS "create_date", "test_new_api_modified_line"."write_uid" AS "write_uid", + "test_new_api_modified_line"."write_date" AS "write_date" + FROM "test_new_api_modified_line" + WHERE "test_new_api_modified_line".id IN %s + """], flush=False): + # One query to fetch `parent_id` in order to invalidate `total_price` recursively + # but because no record has already recompute `total_price` no need to invalidate recursivly + self.modified_line_a_child_child.price = 4 + + self.assertEqual(self.modified_line_a.total_price, 7) + self.assertEqual(self.modified_line_a.total_price_quantity, 35) + self.assertEqual(self.modified_line_a_child.total_price, 6) + self.assertEqual(self.modified_line_a_child.total_price_quantity, 30) + + def test_modified_trigger_recursive_fill_cache(self): + self.assertEqual(self.modified_line_a.total_price, 6) + self.assertEqual(self.modified_line_a.total_price_quantity, 30) + with self.assertQueryCount(0, flush=False): + # No query because the `modified_line_a.total_price` has fetch every data needed + self.modified_line_a_child_child.price = 4 + + self.assertEqual(self.modified_line_a.total_price_quantity, 35) + self.assertEqual(self.modified_line_a.total_price, 7) + + def test_modified_trigger_recursive_partial_invalidate(self): + self.assertEqual(self.modified_line_a_child.total_price_quantity, 25) + self.modified_line_a_child_child.invalidate_recordset() + + self.modified_line_a_child.price + with self.assertQueries([""" + SELECT "test_new_api_modified_line"."id" AS "id", "test_new_api_modified_line"."modified_id" AS "modified_id", + "test_new_api_modified_line"."quantity" AS "quantity", "test_new_api_modified_line"."price" AS "price", + "test_new_api_modified_line"."parent_id" AS "parent_id", "test_new_api_modified_line"."create_uid" AS "create_uid", + "test_new_api_modified_line"."create_date" AS "create_date", "test_new_api_modified_line"."write_uid" AS "write_uid", + "test_new_api_modified_line"."write_date" AS "write_date" + FROM "test_new_api_modified_line" + WHERE "test_new_api_modified_line".id IN %s + """] * 2, flush=False): + # Two requests: + # - one for fetch modified_line_a_child_child data (invalidate just before) + # - one because modified_line_a_child.parent_id (invalidate just before because we invalidate inverse in `_invalidate_cache`, + # see TODO) -> We should change that + self.modified_line_a_child_child.price = 4 + self.assertEqual(self.modified_line_a_child_child.total_price_quantity, 20) + self.assertEqual(self.modified_line_a_child.total_price_quantity, 30) + self.assertEqual(self.modified_line_a.total_price_quantity, 35) + self.assertEqual(self.modified_line_a.total_price, 7)