From 89963e0eddbf36d687799acb6ed1e8486cc178e7 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Wed, 17 Jun 2020 12:18:24 +0000 Subject: [PATCH] [IMP] core: inline SELECT many2one as subquery for one2many fields This avoids the standalone query with a SELECT DISTINCT, which is costly to execute, and enables potential optimizations in the query execution. --- odoo/addons/base/tests/test_expression.py | 48 +++++++++-------------- odoo/osv/expression.py | 36 +++++------------ 2 files changed, 27 insertions(+), 57 deletions(-) diff --git a/odoo/addons/base/tests/test_expression.py b/odoo/addons/base/tests/test_expression.py index 37f9eee2a11..88f86fbcb38 100644 --- a/odoo/addons/base/tests/test_expression.py +++ b/odoo/addons/base/tests/test_expression.py @@ -1280,13 +1280,11 @@ class TestOne2many(TransactionCase): self.Partner.search([('child_ids.bank_ids.sanitized_acc_number', 'like', '12')]) with self.assertQueries([''' - SELECT DISTINCT "partner_id" - FROM "res_partner_bank" - WHERE "id" IN %s - ''', ''' SELECT "res_partner".id FROM "res_partner" - WHERE ("res_partner"."id" IN (%s)) + WHERE ("res_partner"."id" IN ( + SELECT "partner_id" FROM "res_partner_bank" WHERE "id" IN %s + )) ORDER BY "res_partner"."display_name" ''']): self.Partner.search([('bank_ids', 'in', self.partner.bank_ids.ids)]) @@ -1296,14 +1294,12 @@ class TestOne2many(TransactionCase): FROM "res_partner_bank" WHERE ("res_partner_bank"."sanitized_acc_number"::text LIKE %s) ORDER BY "res_partner_bank"."id" - ''', ''' - SELECT DISTINCT "partner_id" - FROM "res_partner_bank" - WHERE "id" IN %s ''', ''' SELECT "res_partner".id FROM "res_partner" - WHERE ("res_partner"."id" IN (%s)) + WHERE ("res_partner"."id" IN ( + SELECT "partner_id" FROM "res_partner_bank" WHERE "id" IN %s + )) ORDER BY "res_partner"."display_name" ''']): self.Partner.search([('bank_ids.sanitized_acc_number', 'like', '12')]) @@ -1313,23 +1309,19 @@ class TestOne2many(TransactionCase): FROM "res_partner_bank" WHERE ("res_partner_bank"."sanitized_acc_number"::text LIKE %s) ORDER BY "res_partner_bank"."id" - ''', ''' - SELECT DISTINCT "partner_id" - FROM "res_partner_bank" - WHERE "id" IN %s ''', ''' SELECT "res_partner".id FROM "res_partner" - WHERE ("res_partner"."id" IN (%s)) + WHERE ("res_partner"."id" IN ( + SELECT "partner_id" FROM "res_partner_bank" WHERE "id" IN %s + )) ORDER BY "res_partner"."id" - ''', ''' - SELECT DISTINCT "parent_id" - FROM "res_partner" - WHERE "id" IN %s ''', ''' SELECT "res_partner".id FROM "res_partner" - WHERE ("res_partner"."id" IN (%s)) + WHERE ("res_partner"."id" IN ( + SELECT "parent_id" FROM "res_partner" WHERE "id" IN %s + )) ORDER BY "res_partner"."display_name" ''']): self.Partner.search([('child_ids.bank_ids.sanitized_acc_number', 'like', '12')]) @@ -1342,13 +1334,11 @@ class TestOne2many(TransactionCase): self.Partner.search([('child_ids.bank_ids.sanitized_acc_number', 'like', '12')]) with self.assertQueries([''' - SELECT DISTINCT "partner_id" - FROM "res_partner_bank" - WHERE "id" IN %s - ''', ''' SELECT "res_partner".id FROM "res_partner" - WHERE ("res_partner"."id" IN (%s)) + WHERE ("res_partner"."id" IN ( + SELECT "partner_id" FROM "res_partner_bank" WHERE "id" IN %s + )) ORDER BY "res_partner"."display_name" ''']): self.Partner.search([('bank_ids', 'in', self.partner.bank_ids.ids)]) @@ -1464,14 +1454,12 @@ class TestOne2many(TransactionCase): FROM "res_partner_bank" WHERE ("res_partner_bank"."sanitized_acc_number"::text LIKE %s) ORDER BY "res_partner_bank"."sequence", "res_partner_bank"."id" - ''', ''' - SELECT DISTINCT "partner_id" - FROM "res_partner_bank" - WHERE "id" IN %s ''', ''' SELECT "res_partner".id FROM "res_partner" - WHERE ("res_partner"."id" IN (%s)) + WHERE ("res_partner"."id" IN ( + SELECT "partner_id" FROM "res_partner_bank" WHERE "id" IN %s + )) ORDER BY "res_partner"."display_name" ''']): self.Partner.search([('bank_ids', 'like', '12')]) diff --git a/odoo/osv/expression.py b/odoo/osv/expression.py index 14d38dedbba..3b5d509df61 100644 --- a/odoo/osv/expression.py +++ b/odoo/osv/expression.py @@ -447,24 +447,6 @@ def check_leaf(element, internal=False): # SQL utils # -------------------------------------------------- -def select_from_where(cr, select_field, from_table, where_field, where_ids, where_operator): - # todo: merge into parent query as sub-query - res = [] - if where_ids: - if where_operator in ['<', '>', '>=', '<=']: - cr.execute('SELECT DISTINCT "%s" FROM "%s" WHERE "%s" %s %%s' % \ - (select_field, from_table, where_field, where_operator), - (where_ids[0],)) # TODO shouldn't this be min/max(where_ids) ? - res = [r[0] for r in cr.fetchall()] - else: # TODO where_operator is supposed to be 'in'? It is called with child_of... - for i in range(0, len(where_ids), cr.IN_MAX): - subids = where_ids[i:i + cr.IN_MAX] - cr.execute('SELECT DISTINCT "%s" FROM "%s" WHERE "%s" IN %%s' % \ - (select_field, from_table, where_field), (tuple(subids),)) - res.extend([r[0] for r in cr.fetchall()]) - return res - - def get_unaccent_wrapper(cr): if odoo.registry(cr.dbname).has_unaccent: return lambda x: "unaccent(%s)" % (x,) @@ -816,18 +798,18 @@ class expression(object): if ids2 and inverse_is_int and domain: ids2 = comodel.search([('id', 'in', ids2)] + domain, order='id').ids - # determine ids1 in model related to ids2 - if not ids2: - ids1 = [] - elif comodel._fields[field.inverse_name].store: - ids1 = select_from_where(cr, field.inverse_name, comodel._table, 'id', ids2, operator) + if ids2 and comodel._fields[field.inverse_name].store: + op1 = 'not inselect' if operator in NEGATIVE_TERM_OPERATORS else 'inselect' + subquery = 'SELECT "%s" FROM "%s" WHERE "id" IN %%s' % (field.inverse_name, comodel._table) + subparams = [tuple(ids2)] + push(('id', op1, (subquery, subparams)), model, alias, internal=True) else: + # determine ids1 in model related to ids2 recs = comodel.browse(ids2).sudo().with_context(prefetch_fields=False) ids1 = unwrap_inverse(recs.mapped(field.inverse_name)) - - # rewrite condition in terms of ids1 - op1 = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in' - push(('id', op1, ids1), model, alias) + # rewrite condition in terms of ids1 + op1 = 'not in' if operator in NEGATIVE_TERM_OPERATORS else 'in' + push(('id', op1, ids1), model, alias) else: if comodel._fields[field.inverse_name].store and not (inverse_is_int and domain):