diff --git a/src/util/models.py b/src/util/models.py index 5ce231a39..99d71ae89 100644 --- a/src/util/models.py +++ b/src/util/models.py @@ -28,6 +28,7 @@ get_value_or_en_translation, parallel_execute, table_exists, + update_m2m_tables, view_exists, ) @@ -268,7 +269,7 @@ def _replace_model_in_computed_custom_fields(cr, source, target): ) -def rename_model(cr, old, new, rename_table=True): +def rename_model(cr, old, new, rename_table=True, ignored_m2ms=()): """ Rename a model. @@ -285,6 +286,7 @@ def rename_model(cr, old, new, rename_table=True): new_table = table_of_model(cr, new) if new_table != old_table: pg_rename_table(cr, old_table, new_table) + update_m2m_tables(cr, old_table, new_table, ignored_m2ms) updates = [("wkf", "osv")] if table_exists(cr, "wkf") else [] updates += [(ir.table, ir.res_model) for ir in indirect_references(cr) if ir.res_model] diff --git a/src/util/pg.py b/src/util/pg.py index c87079120..cdb436299 100644 --- a/src/util/pg.py +++ b/src/util/pg.py @@ -43,7 +43,7 @@ from .exceptions import MigrationError, SleepyDeveloperError from .helpers import _validate_table, model_of_table -from .misc import Sentinel, log_progress +from .misc import Sentinel, log_progress, version_gte _logger = logging.getLogger(__name__) @@ -1300,6 +1300,80 @@ def create_m2m(cr, m2m, fk1, fk2, col1=None, col2=None): ) +def update_m2m_tables(cr, old_table, new_table, ignored_m2ms=()): + """ + Update m2m table names and columns. + + :param str from_model: model for which the underlying table changed + :param str old_table: former table of the model + :param str new_table: new table of the model + :param list(str) ignored_m2ms: explicit list of m2m tables to ignore + + :meta private: exclude from online docs + """ + if old_table == new_table or not version_gte("10.0"): + return + ignored_m2ms = set(ignored_m2ms) + for orig_m2m_table in get_m2m_tables(cr, new_table): + if orig_m2m_table in ignored_m2ms: + continue + m = re.match(r"^(\w+)_{0}_rel|{0}_(\w+)_rel$".format(re.escape(old_table)), orig_m2m_table) + if m: + m2m_table = "{}_{}_rel".format(*sorted([m.group(1) or m.group(2), new_table])) + # Due to the 63 chars limit in generated constraint names, for long table names the FK + # constraint is dropped when renaming the table. We need the constraint to correctly + # identify the FK targets. The FK constraints will be dropped and recreated below. + rename_table(cr, orig_m2m_table, m2m_table, remove_constraints=False) + _logger.info("Renamed m2m table %s to %s", orig_m2m_table, m2m_table) + else: + m2m_table = orig_m2m_table + for m2m_col in get_columns(cr, m2m_table).iter_unquoted(): + col_info = target_of(cr, m2m_table, m2m_col) + if not col_info or col_info[0] != new_table or col_info[1] != "id": + continue + old_col, new_col = map("{}_id".format, [old_table, new_table]) + if m2m_col != old_col: + _logger.warning( + "Possibly missing rename: the column %s of m2m table %s references the table %s", + m2m_col, + m2m_table, + new_table, + ) + continue + old_constraint = col_info[2] + cr.execute( + """ + SELECT c.confdeltype + FROM pg_constraint c + JOIN pg_class t + ON c.conrelid = t.oid + WHERE t.relname = %s + AND c.conname = %s + """, + [m2m_table, old_constraint], + ) + on_delete = cr.fetchone()[0][0] + query = format_query( + cr, + """ + ALTER TABLE {m2m_table} + RENAME COLUMN {old_col} TO {new_col}; + + ALTER TABLE {m2m_table} + DROP CONSTRAINT {old_constraint}, + ADD FOREIGN KEY ({new_col}) REFERENCES {new_table} (id) ON DELETE {del_action} + """, + m2m_table=m2m_table, + old_col=old_col, + new_col=new_col, + old_constraint=old_constraint, + new_table=new_table, + del_action=SQLStr("RESTRICT") if on_delete == "r" else SQLStr("CASCADE"), + ) + cr.execute(query) + _logger.info("Renamed m2m column of table %s from %s to %s", m2m_table, old_col, new_col) + + def fixup_m2m(cr, m2m, fk1, fk2, col1=None, col2=None): if col1 is None: col1 = "%s_id" % fk1