diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index add464ff833..fe655dbc362 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -10,7 +10,7 @@ from collections import defaultdict from odoo import _, api, Command, fields, models, modules, tools from odoo.exceptions import AccessError from odoo.osv import expression -from odoo.tools.misc import clean_context, groupby as tools_groupby +from odoo.tools import clean_context, groupby as tools_groupby, SQL _logger = logging.getLogger(__name__) _image_dataurl = re.compile(r'(data:image/[a-z]+?);base64,([a-z0-9+/\n]{3,}=*)\n*([\'"])(?: data-filename="([^"]*)")?', re.I) @@ -294,23 +294,34 @@ class Message(models.Model): allowed_ids = set() model_ids = defaultdict(lambda: defaultdict(set)) - rel_alias = query.left_join( - self._table, 'id', 'mail_message_res_partner_rel', 'mail_message_id', 'partner_ids', - '{rhs}.res_partner_id = %s', [pid], - ) - notif_alias = query.left_join( - self._table, 'id', 'mail_notification', 'mail_message_id', 'notification_ids', - '{rhs}.res_partner_id = %s', [pid], - ) - query_str, params = query.select( - f'"{self._table}"."id"', - f'"{self._table}"."model"', - f'"{self._table}"."res_id"', - f'"{self._table}"."author_id"', - f'"{self._table}"."message_type"', - f'COALESCE("{rel_alias}"."res_partner_id", "{notif_alias}"."res_partner_id")', - ) - self.env.cr.execute(query_str, params) + rel_alias = query.make_alias(self._table, 'partner_ids') + query.add_join("LEFT JOIN", rel_alias, 'mail_message_res_partner_rel', SQL( + "%s = %s AND %s = %s", + SQL.identifier(self._table, 'id'), + SQL.identifier(rel_alias, 'mail_message_id'), + SQL.identifier(rel_alias, 'res_partner_id'), + pid, + )) + notif_alias = query.make_alias(self._table, 'notification_ids') + query.add_join("LEFT JOIN", notif_alias, 'mail_notification', SQL( + "%s = %s AND %s = %s", + SQL.identifier(self._table, 'id'), + SQL.identifier(notif_alias, 'mail_message_id'), + SQL.identifier(notif_alias, 'res_partner_id'), + pid, + )) + self.env.cr.execute(query.select( + SQL.identifier(self._table, 'id'), + SQL.identifier(self._table, 'model'), + SQL.identifier(self._table, 'res_id'), + SQL.identifier(self._table, 'author_id'), + SQL.identifier(self._table, 'message_type'), + SQL( + "COALESCE(%s, %s)", + SQL.identifier(rel_alias, 'res_partner_id'), + SQL.identifier(notif_alias, 'res_partner_id'), + ), + )) for id_, model, res_id, author_id, message_type, partner_id in self.env.cr.fetchall(): ids.append(id_) if author_id == pid: diff --git a/odoo/models.py b/odoo/models.py index 7ca689c4c35..4627fd60da1 100644 --- a/odoo/models.py +++ b/odoo/models.py @@ -2780,18 +2780,23 @@ class BaseModel(metaclass=MetaModel): comodel = self.env[field.comodel_name] subquery = Query(self.env.cr, comodel._table) comodel._apply_ir_rules(subquery) - # add the extra join condition only if there is an actual subquery - extra, extra_params = None, () - if subquery.where_clause: - subquery_str, extra_params = subquery.select() - extra = '"{rhs}"."%s" IN (%s)' % (field.column2, subquery_str) # LEFT JOIN field_relation ON # alias.id = field_relation.field_column1 # AND field_relation.field_column2 IN (subquery) - rel_alias = query.left_join( - alias, 'id', field.relation, field.column1, field.name, - extra=extra, extra_params=extra_params, + rel_alias = query.make_alias(alias, field.name) + condition = SQL( + "%s = %s", + SQL.identifier(alias, 'id'), + SQL.identifier(rel_alias, field.column1), ) + if subquery.where_clause: + condition = SQL( + "%s AND %s IN %s", + condition, + SQL.identifier(rel_alias, field.column2), + subquery.subselect(), + ) + query.add_join("LEFT JOIN", rel_alias, field.relation, condition) return '"%s"."%s"' % (rel_alias, field.column2) elif field.translate and not self.env.context.get('prefetch_langs'): lang = self.env.lang or 'en_US' diff --git a/odoo/tools/query.py b/odoo/tools/query.py index 29f0cddfb94..44c492b1671 100644 --- a/odoo/tools/query.py +++ b/odoo/tools/query.py @@ -110,10 +110,11 @@ class Query(object): self._where_clauses.append(SQL(where_clause, *where_params)) self._ids = None - def join(self, lhs_alias, lhs_column, rhs_table, rhs_column, link, extra=None, extra_params=()): + def join(self, lhs_alias: str, lhs_column: str, rhs_table: str, rhs_column: str, link: str): """ Perform a join between a table already present in the current Query object and - another table. + another table. This method is essentially a shortcut for methods :meth:`~.make_alias` + and :meth:`~.add_join`. :param str lhs_alias: alias of a table already defined in the current Query object. :param str lhs_column: column of `lhs_alias` to be used for the join's ON condition. @@ -121,59 +122,24 @@ class Query(object): :param str rhs_column: column of `rhs_alias` to be used for the join's ON condition. :param str link: used to generate the alias for the joined table, this string should represent the relationship (the link) between both tables. - :param str extra: an sql string of a predicate or series of predicates to append to the - join's ON condition, `lhs_alias` and `rhs_alias` can be injected if the string uses - the `lhs` and `rhs` variables with the `str.format` syntax. e.g.:: - - query.join(..., extra="{lhs}.name != {rhs}.name OR ...", ...) - - :param tuple extra_params: a tuple of values to be interpolated into `extra`, this is - done by psycopg2. - - Full example: - - >>> rhs_alias = query.join( - ... "res_users", - ... "partner_id", - ... "res_partner", - ... "id", - ... "partner_id", # partner_id is the "link" from res_users to res_partner - ... "{lhs}.\"name\" != %s", - ... ("Mitchell Admin",), - ... ) - >>> rhs_alias - res_users_res_partner__partner_id - - From the example above, the resulting query would be something like:: - - SELECT ... - FROM "res_users" AS "res_users" - JOIN "res_partner" AS "res_users_res_partner__partner_id" - ON "res_users"."partner_id" = "res_users_res_partner__partner_id"."id" - AND "res_users"."name" != 'Mitchell Admin' - WHERE ... - """ - return self._join('JOIN', lhs_alias, lhs_column, rhs_table, rhs_column, link, extra, extra_params) + assert lhs_alias in self._tables or lhs_alias in self._joins, "Alias %r not in %s" % (lhs_alias, str(self)) + rhs_alias = self.make_alias(lhs_alias, link) + condition = SQL("%s = %s", SQL.identifier(lhs_alias, lhs_column), SQL.identifier(rhs_alias, rhs_column)) + self.add_join('JOIN', rhs_alias, rhs_table, condition) + return rhs_alias - def left_join(self, lhs_alias, lhs_column, rhs_table, rhs_column, link, extra=None, extra_params=()): + def left_join(self, lhs_alias: str, lhs_column: str, rhs_table: str, rhs_column: str, link: str): """ Add a LEFT JOIN to the current table (if necessary), and return the alias corresponding to ``rhs_table``. See the documentation of :meth:`join` for a better overview of the arguments and what they do. """ - return self._join('LEFT JOIN', lhs_alias, lhs_column, rhs_table, rhs_column, link, extra, extra_params) - - def _join(self, kind, lhs_alias, lhs_column, rhs_table, rhs_column, link, extra=None, extra_params=()): assert lhs_alias in self._tables or lhs_alias in self._joins, "Alias %r not in %s" % (lhs_alias, str(self)) rhs_alias = self.make_alias(lhs_alias, link) condition = SQL("%s = %s", SQL.identifier(lhs_alias, lhs_column), SQL.identifier(rhs_alias, rhs_column)) - if extra: - extra = SQL(extra, *extra_params) - extra = SQL(extra.code.format(lhs=lhs_alias, rhs=rhs_alias), *extra.params) - condition = SQL("%s AND %s", condition, extra) - self.add_join(kind, rhs_alias, rhs_table, condition) + self.add_join('LEFT JOIN', rhs_alias, rhs_table, condition) return rhs_alias @property