[IMP] core: use SQL wrapper in fields

Part-of: odoo/odoo#138019
This commit is contained in:
Raphael Collet
2023-10-20 09:35:18 +00:00
parent 2c09f5ed68
commit 33d1303a2d
+61 -52
View File
@@ -24,7 +24,6 @@ import psycopg2
import pytz
from markupsafe import Markup
from psycopg2.extras import Json as PsycopgJson
from psycopg2.sql import SQL, Identifier
from difflib import get_close_matches, unified_diff
from hashlib import sha256
@@ -32,7 +31,7 @@ from .models import check_property_field_value_name
from .netsvc import ColoredFormatter, GREEN, RED, DEFAULT, COLOR_PATTERN
from .tools import (
float_repr, float_round, float_compare, float_is_zero, human_size,
pg_varchar, ustr, OrderedSet, pycompat, sql, date_utils, unique,
pg_varchar, ustr, OrderedSet, pycompat, sql, SQL, date_utils, unique,
image_process, merge_sequences, SQL_ORDER_BY_TYPE, is_list_of, has_list_types,
html_normalize, html_sanitize,
)
@@ -1079,17 +1078,16 @@ class Field(MetaField('DummyField', (object,), {})):
""" Compute a stored related field directly in SQL. """
comodel = model.env[self.related_field.model_name]
join_field, comodel_field = self.related.split('.')
model.env.cr.execute("""
UPDATE "{model_table}" AS x
SET "{model_field}" = y."{comodel_field}"
FROM "{comodel_table}" AS y
WHERE x."{join_field}" = y.id
""".format(
model_table=model._table,
model_field=self.name,
comodel_table=comodel._table,
comodel_field=comodel_field,
join_field=join_field,
model.env.cr.execute(SQL(
""" UPDATE %(model_table)s AS x
SET %(model_field)s = y.%(comodel_field)s
FROM %(comodel_table)s AS y
WHERE x.%(join_field)s = y.id """,
model_table=SQL.identifier(model._table),
model_field=SQL.identifier(self.name),
comodel_table=SQL.identifier(comodel._table),
comodel_field=SQL.identifier(comodel_field),
join_field=SQL.identifier(join_field),
))
############################################################################
@@ -1765,7 +1763,12 @@ class _String(Field):
# assert (self.translate and self.store and record)
record.flush_recordset([self.name])
cr = record.env.cr
cr.execute(f'SELECT "{self.name}" FROM "{record._table}" WHERE id = %s', (record.id,))
cr.execute(SQL(
"SELECT %s FROM %s WHERE id = %s",
SQL.identifier(self.name),
SQL.identifier(record._table),
record.id,
))
res = cr.fetchone()
return res[0] if res else None
@@ -4718,14 +4721,17 @@ class Many2many(_RelationalMulti):
model, self.relation, self._module)
comodel = model.env[self.comodel_name]
if not sql.table_exists(cr, self.relation):
query = psycopg2.sql.SQL("""
CREATE TABLE "{rel}" ("{id1}" INTEGER NOT NULL,
"{id2}" INTEGER NOT NULL,
PRIMARY KEY("{id1}","{id2}"));
COMMENT ON TABLE "{rel}" IS %s;
CREATE INDEX ON "{rel}" ("{id2}","{id1}");
""").format(rel=psycopg2.sql.SQL(self.relation), id1=psycopg2.sql.SQL(self.column1), id2=psycopg2.sql.SQL(self.column2))
cr.execute(query, ['RELATION BETWEEN %s AND %s' % (model._table, comodel._table)])
cr.execute(SQL(
""" CREATE TABLE %(rel)s (%(id1)s INTEGER NOT NULL,
%(id2)s INTEGER NOT NULL,
PRIMARY KEY(%(id1)s, %(id2)s));
COMMENT ON TABLE %(rel)s IS %(comment)s;
CREATE INDEX ON %(rel)s (%(id2)s, %(id1)s); """,
rel=SQL.identifier(self.relation),
id1=SQL.identifier(self.column1),
id2=SQL.identifier(self.column2),
comment=f"RELATION BETWEEN {model._table} AND {comodel._table}",
))
_schema.debug("Create table %r: m2m relation between %r and %r", self.relation, model._table, comodel._table)
model.pool.post_init(self.update_db_foreign_keys, model)
return True
@@ -4754,24 +4760,26 @@ class Many2many(_RelationalMulti):
context = {'active_test': False}
context.update(self.context)
comodel = records.env[self.comodel_name].with_context(**context)
# make the query for the lines
domain = self.get_domain_list(records)
comodel._flush_search(domain, order=comodel._order)
wquery = comodel._where_calc(domain)
comodel._apply_ir_rules(wquery, 'read')
order_by = comodel._generate_order_by(None, wquery)
from_c, where_c, where_params = wquery.get_sql()
query = """ SELECT {rel}.{id1}, {rel}.{id2} FROM {rel}, {from_c}
WHERE {where_c} AND {rel}.{id1} IN %s AND {rel}.{id2} = {tbl}.id
{order_by}
""".format(rel=self.relation, id1=self.column1, id2=self.column2,
tbl=comodel._table, from_c=from_c, where_c=where_c or '1=1',
order_by=order_by)
where_params.append(tuple(records.ids))
query = comodel._where_calc(domain)
comodel._apply_ir_rules(query, 'read')
query.order = comodel._generate_order_by(comodel._order, query).replace(' ORDER BY ', '')
# retrieve lines and group them by record
# join with many2many relation table
sql_id1 = SQL.identifier(self.relation, self.column1)
sql_id2 = SQL.identifier(self.relation, self.column2)
query.add_join('JOIN', self.relation, None, SQL(
"%s = %s", sql_id2, SQL.identifier(comodel._table, 'id'),
))
query.add_where(SQL("%s IN %s", sql_id1, tuple(records.ids)))
# retrieve pairs (record, line) and group by record
group = defaultdict(list)
records._cr.execute(query, where_params)
for row in records._cr.fetchall():
records.env.cr.execute(query.select(sql_id1, sql_id2))
for row in records.env.cr.fetchall():
group[row[0]].append(row[1])
# store result in cache
@@ -4869,12 +4877,13 @@ class Many2many(_RelationalMulti):
pairs = [(x, y) for x, ys in new_relation.items() for y in ys - old_relation[x]]
if pairs:
if self.store:
query = SQL("INSERT INTO {} ({}, {}) VALUES %s ON CONFLICT DO NOTHING").format(
Identifier(self.relation),
Identifier(self.column1),
Identifier(self.column2),
)
cr.execute_values(query, pairs)
cr.execute(SQL(
"INSERT INTO %s (%s, %s) VALUES %s ON CONFLICT DO NOTHING",
SQL.identifier(self.relation),
SQL.identifier(self.column1),
SQL.identifier(self.column2),
SQL(", ").join(pairs),
))
# update the cache of inverse fields
y_to_xs = defaultdict(set)
@@ -4912,16 +4921,16 @@ class Many2many(_RelationalMulti):
for y, xs in y_to_xs.items():
xs_to_ys[frozenset(xs)].add(y)
# delete the rows where (id1 IN xs AND id2 IN ys) OR ...
COND = SQL("{} IN %s AND {} IN %s").format(
Identifier(self.column1),
Identifier(self.column2),
)
query = SQL("DELETE FROM {} WHERE {}").format(
Identifier(self.relation),
SQL(" OR ").join([COND] * len(xs_to_ys)),
)
params = [arg for xs, ys in xs_to_ys.items() for arg in [tuple(xs), tuple(ys)]]
cr.execute(query, params)
cr.execute(SQL(
"DELETE FROM %s WHERE %s",
SQL.identifier(self.relation),
SQL(" OR ").join(
SQL("%s IN %s AND %s IN %s",
SQL.identifier(self.column1), tuple(xs),
SQL.identifier(self.column2), tuple(ys))
for xs, ys in xs_to_ys.items()
),
))
# update the cache of inverse fields
for invf in records.pool.field_inverses[self]: