Skip to content

Commit aad0d58

Browse files
aj-fuentesKangOl
andcommitted
[FIX] util/records: ensure no duplicates for m2m when replacing refs
There are two ways an update of references could fail in an m2m table. If there is a record already in the table matching the currently updated row. Example: ``` mapping old new 1 3 2 3 Fails for: m2m fk col2 1 x -updated-> 3 x !error 3 x ``` Or, if we have multiple ids mapped to the same new one. Example: ``` mapping old new 1 3 2 3 Fails for: m2m fk col2 1 x -updated-> 3 x 2 x -updated-> 3 x !error ``` Part-of: #33 Co-authored-by: "Christophe Simonis" <[email protected]>
1 parent 2bd20f0 commit aad0d58

File tree

2 files changed

+54
-6
lines changed

2 files changed

+54
-6
lines changed

src/base/tests/test_util.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,3 +1012,26 @@ def test_format_ColumnList(self):
10121012
util.format_query(cr, "SELECT {c} id", c=no_columns.using(trailing_comma=True)),
10131013
"SELECT id",
10141014
)
1015+
1016+
1017+
class TestReplaceRecordReferences(UnitTestCase):
1018+
def test_m2m_no_conflict(self):
1019+
cr = self.env.cr
1020+
g1 = self.env["res.groups"].create({"name": "G1"})
1021+
g2 = self.env["res.groups"].create({"name": "G2"})
1022+
g3 = self.env["res.groups"].create({"name": "G3"})
1023+
mapping = {g1.id: g3.id, g2.id: g3.id}
1024+
1025+
u1 = self.env["res.users"].create({"login": "U1", "name": "U1"})
1026+
u1.groups_id = g1 | g3
1027+
self.assertEqual(u1.groups_id.ids, [g1.id, g3.id])
1028+
util.replace_record_references_batch(cr, mapping, "res.groups")
1029+
util.invalidate(u1)
1030+
self.assertEqual(u1.groups_id.ids, [g3.id])
1031+
1032+
u2 = self.env["res.users"].create({"login": "U2", "name": "U2"})
1033+
u2.groups_id = g1 | g2
1034+
self.assertEqual(u2.groups_id.ids, [g1.id, g2.id])
1035+
util.replace_record_references_batch(cr, mapping, "res.groups")
1036+
util.invalidate(u2)
1037+
self.assertEqual(u2.groups_id.ids, [g3.id])

src/util/records.py

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
column_updatable,
3535
explode_execute,
3636
explode_query_range,
37+
format_query,
3738
get_columns,
3839
get_fk,
3940
get_value_or_en_translation,
@@ -1024,7 +1025,7 @@ def replace_record_references_batch(cr, id_mapping, model_src, model_dst=None, r
10241025
fk_def = []
10251026

10261027
model_src_table = table_of_model(cr, model_src)
1027-
for table, fk, _, _ in get_fk(cr, model_src_table):
1028+
for table, fk, _, _ in get_fk(cr, model_src_table, quote_ident=False):
10281029
if table in ignores:
10291030
continue
10301031
query = """
@@ -1036,9 +1037,33 @@ def replace_record_references_batch(cr, id_mapping, model_src, model_dst=None, r
10361037

10371038
if not column_exists(cr, table, "id"):
10381039
# seems to be a m2m table. Avoid duplicated entries
1039-
cols = get_columns(cr, table, ignore=(fk,))
1040-
assert len(cols) == 1 # it's a m2, should have only 2 columns
1041-
col2 = cols[0]
1040+
(col2,) = get_columns(cr, table, ignore=(fk,)).iter_unquoted()
1041+
1042+
# handle possible duplicates after update of m2m table
1043+
cr.execute(
1044+
format_query(
1045+
cr,
1046+
"""
1047+
WITH rr2 AS (
1048+
SELECT array_agg(t.{fk} ORDER BY t.{fk}) AS olds,
1049+
r.new AS new,
1050+
t.{col2} AS col2
1051+
FROM {table} t
1052+
JOIN _upgrade_rrr r
1053+
ON r.old = t.{fk}
1054+
GROUP BY r.new, t.{col2}
1055+
)
1056+
DELETE
1057+
FROM {table} t
1058+
USING rr2 r
1059+
WHERE t.{col2} = r.col2
1060+
AND t.{fk} = ANY(r.olds[2:])
1061+
""",
1062+
table=table,
1063+
fk=fk,
1064+
col2=col2,
1065+
)
1066+
)
10421067
query += """
10431068
AND NOT EXISTS(SELECT 1 FROM {table} e WHERE e.{col2} = t.{col2} AND e.{fk} = r.new);
10441069
@@ -1062,10 +1087,10 @@ def replace_record_references_batch(cr, id_mapping, model_src, model_dst=None, r
10621087
AND t.{fk} = t.{col2};
10631088
"""
10641089

1065-
cr.execute(query.format(table=table, fk=fk, col2=col2))
1090+
cr.execute(format_query(cr, query, table=table, fk=fk, col2=col2))
10661091

10671092
else: # it's a model
1068-
fmt_query = cr.mogrify(query.format(table=table, fk=fk)).decode()
1093+
fmt_query = cr.mogrify(format_query(cr, query, table=table, fk=fk)).decode()
10691094
parallel_execute(cr, explode_query_range(cr, fmt_query, table=table, alias="t"))
10701095

10711096
# track default values to update

0 commit comments

Comments
 (0)