Files
odoo_source/addons/web/controllers/action.py
T
Julien CastiauxandRaphaël Collet 5a998694a6 [IMP] core: merge subqueries WHERE clauses
Rationale
---------

Given the following domain:

    ['|',
        ('company_id.name', 'like', 'BE'),
        ('company_id.city', 'like', 'Brussels')]

The ORM would generate the following SQL query:

    SELECT "res_partner"."id"
    FROM "res_partner"
    WHERE "res_partner"."company_id" IN (
        SELECT "res_company"."id"
        FROM "res_company"
        WHERE "res_company"."name" like 'BE'
    ) OR "res_partner"."company_id" IN (
        SELECT "res_company"."id"
        FROM "res_company"
        WHERE "res_company"."email" like 'help@odoo.com'
    );

Which is sub-optiomal as the WHERE clause of the two subqueries could be
regrouped inside of a single query like so:

    SELECT "res_partner"."id"
    FROM "res_partner"
    WHERE "res_partner"."company_id" IN (
        SELECT "res_company"."id"
        FROM "res_company" WHERE (
            "res_company"."name" like 'BE'
            OR "res_company"."email" like 'help@odoo.com'
        )
    );

Postgres-wise, it is faster to execute the latter query than the former
one. This commit is about optimizing the ORM so that it generates
queries where the WHERE clause of compatible subqueries are grouped
together.

Technical solution
------------------

The solution explored by this work is to replace the regular relational
field accesses (over a dotted path) by a new `any` operator that look as
follow:

    (relational_field, 'any', domain)

For instance, the above `('company_id.name', 'like', 'BE')` leaf becomes
`('company_id', 'any', [('name', 'like', 'BE')]` using `any`.

Having a domain as right-hand-side allow for the combination of the
domains of same compatible relations:

    [('company_id', 'any', ['|',
        ('name', 'like', 'BE'),
        ('city', 'like', 'Brussels')])

Having combined domains allows for generating better SQL queries with
minimal changes to the expression parsing algorithm, which can only
translate a single leaf at a time to SQL. With the new `any` operator,
it is still a single leaf but the right-hand-side contains the merged
domain, thus the combined SQL WHERE clause is immediately generated.

task-id-3234671

Part-of: odoo/odoo#118067
Co-authored-by: Raphaël Collet <rco@odoo.com>
2023-04-27 10:12:57 +02:00

46 lines
1.6 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import logging
from odoo import _
from odoo.exceptions import MissingError
from odoo.http import Controller, request, route
from .utils import clean_action
_logger = logging.getLogger(__name__)
class Action(Controller):
@route('/web/action/load', type='json', auth="user")
def load(self, action_id, additional_context=None):
Actions = request.env['ir.actions.actions']
value = False
try:
action_id = int(action_id)
except ValueError:
try:
action = request.env.ref(action_id)
assert action._name.startswith('ir.actions.')
action_id = action.id
except Exception as exc:
raise MissingError(_("The action %r does not exist.", action_id)) from exc
base_action = Actions.browse([action_id]).sudo().read(['type'])
if base_action:
action_type = base_action[0]['type']
if action_type == 'ir.actions.report':
request.update_context(bin_size=True)
if additional_context:
request.update_context(**additional_context)
action = request.env[action_type].sudo().browse([action_id]).read()
if action:
value = clean_action(action[0], env=request.env)
return value
@route('/web/action/run', type='json', auth="user")
def run(self, action_id):
action = request.env['ir.actions.server'].browse([action_id])
result = action.run()
return clean_action(result, env=action.env) if result else False