]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
suffix index names with "_history" just like tables
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 23 Jan 2024 19:06:01 +0000 (14:06 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 23 Jan 2024 21:49:47 +0000 (16:49 -0500)
Fixed regression in history_meta example where the use of
:meth:`_schema.MetaData.to_metadata` to make a copy of the history table
would also copy indexes (which is a good thing), but causing naming
conflicts indexes regardless of naming scheme used for those indexes. A
"_history" suffix is now added to these indexes in the same way as is
achieved for the table name.

Fixes: #10920
Change-Id: I78823650956ff979d500bedbdbce261048894ce9

doc/build/changelog/unreleased_20/10920.rst [new file with mode: 0644]
examples/versioned_history/history_meta.py
examples/versioned_history/test_versioning.py

diff --git a/doc/build/changelog/unreleased_20/10920.rst b/doc/build/changelog/unreleased_20/10920.rst
new file mode 100644 (file)
index 0000000..e7bc7b8
--- /dev/null
@@ -0,0 +1,11 @@
+.. change::
+    :tags: bug, examples
+    :tickets: 10920
+
+    Fixed regression in history_meta example where the use of
+    :meth:`_schema.MetaData.to_metadata` to make a copy of the history table
+    would also copy indexes (which is a good thing), but causing naming
+    conflicts indexes regardless of naming scheme used for those indexes. A
+    "_history" suffix is now added to these indexes in the same way as is
+    achieved for the table name.
+
index 806267cb41424a272f5970fccf4c713f4b633ee7..3f26832b9edfad01c78635d765e63777e5d7dc35 100644 (file)
@@ -56,6 +56,9 @@ def _history_mapper(local_mapper):
             local_mapper.local_table.metadata,
             name=local_mapper.local_table.name + "_history",
         )
+        for idx in history_table.indexes:
+            if idx.name is not None:
+                idx.name += "_history"
 
         for orig_c, history_c in zip(
             local_mapper.local_table.c, history_table.c
index 7b9c82c60faac7e04656527271df03362a7e3af5..ac122581a4fec820856ebe3694ff90ce7b974af8 100644 (file)
@@ -8,11 +8,15 @@ from sqlalchemy import Boolean
 from sqlalchemy import Column
 from sqlalchemy import create_engine
 from sqlalchemy import ForeignKey
+from sqlalchemy import ForeignKeyConstraint
+from sqlalchemy import Index
 from sqlalchemy import inspect
 from sqlalchemy import Integer
 from sqlalchemy import join
 from sqlalchemy import select
 from sqlalchemy import String
+from sqlalchemy import testing
+from sqlalchemy import UniqueConstraint
 from sqlalchemy.orm import clear_mappers
 from sqlalchemy.orm import column_property
 from sqlalchemy.orm import declarative_base
@@ -31,7 +35,6 @@ from sqlalchemy.testing.entities import ComparableEntity
 from .history_meta import Versioned
 from .history_meta import versioned_session
 
-
 warnings.simplefilter("error")
 
 
@@ -127,6 +130,98 @@ class TestVersioning(AssertsCompiledSQL):
             ],
         )
 
+    @testing.variation(
+        "constraint_type",
+        [
+            "index_single_col",
+            "composite_index",
+            "explicit_name_index",
+            "unique_constraint",
+            "unique_constraint_naming_conv",
+            "unique_constraint_explicit_name",
+            "fk_constraint",
+            "fk_constraint_naming_conv",
+            "fk_constraint_explicit_name",
+        ],
+    )
+    def test_index_naming(self, constraint_type):
+        """test #10920"""
+
+        if (
+            constraint_type.unique_constraint_naming_conv
+            or constraint_type.fk_constraint_naming_conv
+        ):
+            self.Base.metadata.naming_convention = {
+                "ix": "ix_%(column_0_label)s",
+                "uq": "uq_%(table_name)s_%(column_0_name)s",
+                "fk": (
+                    "fk_%(table_name)s_%(column_0_name)s"
+                    "_%(referred_table_name)s"
+                ),
+            }
+
+        if (
+            constraint_type.fk_constraint
+            or constraint_type.fk_constraint_naming_conv
+            or constraint_type.fk_constraint_explicit_name
+        ):
+
+            class Related(self.Base):
+                __tablename__ = "related"
+
+                id = Column(Integer, primary_key=True)
+
+        class SomeClass(Versioned, self.Base):
+            __tablename__ = "sometable"
+
+            id = Column(Integer, primary_key=True)
+            x = Column(Integer)
+            y = Column(Integer)
+
+            # Index objects are copied and these have to have a new name
+            if constraint_type.index_single_col:
+                __table_args__ = (
+                    Index(
+                        None,
+                        x,
+                    ),
+                )
+            elif constraint_type.composite_index:
+                __table_args__ = (Index(None, x, y),)
+            elif constraint_type.explicit_name_index:
+                __table_args__ = (Index("my_index", x, y),)
+            # unique constraint objects are discarded.
+            elif (
+                constraint_type.unique_constraint
+                or constraint_type.unique_constraint_naming_conv
+            ):
+                __table_args__ = (UniqueConstraint(x, y),)
+            elif constraint_type.unique_constraint_explicit_name:
+                __table_args__ = (UniqueConstraint(x, y, name="my_uq"),)
+            # foreign key constraint objects are copied and have the same
+            # name, but no database in Core has any problem with this as the
+            # names are local to the parent table.
+            elif (
+                constraint_type.fk_constraint
+                or constraint_type.fk_constraint_naming_conv
+            ):
+                __table_args__ = (ForeignKeyConstraint([x], [Related.id]),)
+            elif constraint_type.fk_constraint_explicit_name:
+                __table_args__ = (
+                    ForeignKeyConstraint([x], [Related.id], name="my_fk"),
+                )
+            else:
+                constraint_type.fail()
+
+        eq_(
+            set(idx.name + "_history" for idx in SomeClass.__table__.indexes),
+            set(
+                idx.name
+                for idx in SomeClass.__history_mapper__.local_table.indexes
+            ),
+        )
+        self.create_tables()
+
     def test_discussion_9546(self):
         class ThingExternal(Versioned, self.Base):
             __tablename__ = "things_external"