]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Correct reflection for composite primary keys
authorfulpm <8397318+fulpm@users.noreply.github.com>
Tue, 20 Oct 2020 21:54:50 +0000 (17:54 -0400)
committerGord Thompson <gord@gordthompson.com>
Wed, 21 Oct 2020 23:47:33 +0000 (17:47 -0600)
Fixes: #5661
### Description
Fixes reflection of composite primary keys to maintain the correct column order in the MSSQL
and SQLite dialects.

Closes: #5662
Pull-request: https://github.com/sqlalchemy/sqlalchemy/pull/5662
Pull-request-sha: b568dec7070b4f3ee46a528bdf16fb237baade2a

Change-Id: I452b23cbf7f389c4a0a34cffce5c32498efe37d2

doc/build/changelog/unreleased_13/5661.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/mssql/base.py
lib/sqlalchemy/dialects/sqlite/base.py
lib/sqlalchemy/testing/suite/test_reflection.py

diff --git a/doc/build/changelog/unreleased_13/5661.rst b/doc/build/changelog/unreleased_13/5661.rst
new file mode 100644 (file)
index 0000000..042b6a4
--- /dev/null
@@ -0,0 +1,6 @@
+.. change::
+    :tags: mssql, sqlite, reflection
+    :tickets: 5661
+
+    Fixed issue with composite primary key columns not being reported
+    in the correct order. Patch courtesy @fulpm.
index c8f2b4ca3b30b826623d6e06b8a753e6ccc5587b..a224c00bbd14973698bdfa5cc7bf2026087679b2 100644 (file)
@@ -3168,15 +3168,19 @@ class MSDialect(default.DefaultDialect):
         C = ischema.key_constraints.alias("C")
 
         # Primary key constraints
-        s = sql.select(
-            C.c.column_name, TC.c.constraint_type, C.c.constraint_name
-        ).where(
-            sql.and_(
-                TC.c.constraint_name == C.c.constraint_name,
-                TC.c.table_schema == C.c.table_schema,
-                C.c.table_name == tablename,
-                C.c.table_schema == owner,
-            ),
+        s = (
+            sql.select(
+                C.c.column_name, TC.c.constraint_type, C.c.constraint_name
+            )
+            .where(
+                sql.and_(
+                    TC.c.constraint_name == C.c.constraint_name,
+                    TC.c.table_schema == C.c.table_schema,
+                    C.c.table_name == tablename,
+                    C.c.table_schema == owner,
+                ),
+            )
+            .order_by(TC.c.constraint_name, C.c.ordinal_position)
         )
         c = connection.execution_options(future_result=True).execute(s)
         constraint_name = None
index 8ef35514abe29afae9a9487745f805fcee1a7607..8a4fbe8e55d3a17df7063288d72f9c8100514dca 100644 (file)
@@ -1853,6 +1853,7 @@ class SQLiteDialect(default.DefaultDialect):
             constraint_name = result.group(1) if result else None
 
         cols = self.get_columns(connection, table_name, schema, **kw)
+        cols.sort(key=lambda col: col.get("primary_key"))
         pkeys = []
         for col in cols:
             if col["primary_key"]:
index f8f93b563540db9c3a85b7a0e8390404bb59e5ba..49aafa879c70f173759bda684089e95ecf75fb64 100644 (file)
@@ -1581,6 +1581,59 @@ class IdentityReflectionTest(fixtures.TablesTest):
                 )
 
 
+class CompositeKeyReflectionTest(fixtures.TablesTest):
+    __backend__ = True
+
+    @classmethod
+    def define_tables(cls, metadata):
+        tb1 = Table(
+            "tb1",
+            metadata,
+            Column("id", Integer),
+            Column("attr", Integer),
+            Column("name", sql_types.VARCHAR(20)),
+            sa.PrimaryKeyConstraint("name", "id", "attr", name="pk_tb1"),
+            schema=None,
+            test_needs_fk=True,
+        )
+        Table(
+            "tb2",
+            metadata,
+            Column("id", Integer, primary_key=True),
+            Column("pid", Integer),
+            Column("pattr", Integer),
+            Column("pname", sql_types.VARCHAR(20)),
+            sa.ForeignKeyConstraint(
+                ["pname", "pid", "pattr"],
+                [tb1.c.name, tb1.c.id, tb1.c.attr],
+                name="fk_tb1_name_id_attr",
+            ),
+            schema=None,
+            test_needs_fk=True,
+        )
+
+    @testing.requires.primary_key_constraint_reflection
+    @testing.provide_metadata
+    def test_pk_column_order(self):
+        # test for issue #5661
+        meta = self.metadata
+        insp = inspect(meta.bind)
+        primary_key = insp.get_pk_constraint(self.tables.tb1.name)
+        eq_(primary_key.get("constrained_columns"), ["name", "id", "attr"])
+
+    @testing.requires.foreign_key_constraint_reflection
+    @testing.provide_metadata
+    def test_fk_column_order(self):
+        # test for issue #5661
+        meta = self.metadata
+        insp = inspect(meta.bind)
+        foreign_keys = insp.get_foreign_keys(self.tables.tb2.name)
+        eq_(len(foreign_keys), 1)
+        fkey1 = foreign_keys[0]
+        eq_(fkey1.get("referred_columns"), ["name", "id", "attr"])
+        eq_(fkey1.get("constrained_columns"), ["pname", "pid", "pattr"])
+
+
 __all__ = (
     "ComponentReflectionTest",
     "QuotedNameArgumentTest",
@@ -1589,4 +1642,5 @@ __all__ = (
     "NormalizedNameTest",
     "ComputedReflectionTest",
     "IdentityReflectionTest",
+    "CompositeKeyReflectionTest",
 )