]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Raise if ForeignKeyConstraint created with different numbers of
authorMike Bayer <mike_mp@zzzcomputing.com>
Thu, 25 May 2017 14:17:11 +0000 (10:17 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Thu, 25 May 2017 20:25:09 +0000 (16:25 -0400)
local and remote columns.

An :class:`.ArgumentError` is now raised if a
:class:`.ForeignKeyConstraint` object is created with a mismatched
number of "local" and "remote" columns, which otherwise causes the
internal state of the constraint to be incorrect.   Note that this
also impacts the condition where a dialect's reflection process
produces a mismatched set of columns for a foreign key constraint.

Downstream DB2 dialect has been reported as potentially causing this
scenario.

Change-Id: Id51c34a6c43749bb582639f9c1dc28723482f0e5
Fixes: #3949
References: #3998
(cherry picked from commit a78718b9340e9840a470300932af178ce57c0f7d)

doc/build/changelog/changelog_11.rst
lib/sqlalchemy/sql/schema.py
test/sql/test_metadata.py

index eb85fad1f58df0da333a167bd913c9a2e84d90ce..e7d278b78e83d8d727abd41c739ee5e5cf208dc4 100644 (file)
         fail for cx_Oracle version 6.0b1 due to the "b" character.  Version
         string parsing is now via a regexp rather than a simple split.
 
+    .. change:: 3949
+        :tags: bug, schema
+        :versions: 1.2.0b1
+        :tickets: 3949
+
+        An :class:`.ArgumentError` is now raised if a
+        :class:`.ForeignKeyConstraint` object is created with a mismatched
+        number of "local" and "remote" columns, which otherwise causes the
+        internal state of the constraint to be incorrect.   Note that this
+        also impacts the condition where a dialect's reflection process
+        produces a mismatched set of columns for a foreign key constraint.
+
     .. change:: 3980
         :tags: bug, ext
         :versions: 1.2.0b1
index 066e85f0bd09bea156cbc0bc4da29fc0cc4fc895..078487becfdf1b7146652b82a5d20bcf5af9d915 100644 (file)
@@ -2855,6 +2855,22 @@ class ForeignKeyConstraint(ColumnCollectionConstraint):
         self.use_alter = use_alter
         self.match = match
 
+        if len(set(columns)) != len(refcolumns):
+            if len(set(columns)) != len(columns):
+                # e.g. FOREIGN KEY (a, a) REFERENCES r (b, c)
+                raise exc.ArgumentError(
+                    "ForeignKeyConstraint with duplicate source column "
+                    "references are not supported."
+                )
+            else:
+                # e.g. FOREIGN KEY (a) REFERENCES r (b, c)
+                # paraphrasing https://www.postgresql.org/docs/9.2/static/\
+                # ddl-constraints.html
+                raise exc.ArgumentError(
+                    "ForeignKeyConstraint number "
+                    "of constrained columns must match the number of "
+                    "referenced columns.")
+
         # standalone ForeignKeyConstraint - create
         # associated ForeignKey objects which will be applied to hosted
         # Column objects (in col.foreign_keys), either now or when attached
index 6d0df3b5f096f4dd0d5a60ab4bb56fc593d84bdc..61fbbc57b46a95d1055c9458783de100fa04e259 100644 (file)
@@ -348,6 +348,29 @@ class MetaDataTest(fixtures.TestBase, ComparesTables):
             getattr, list(a.foreign_keys)[0], "column"
         )
 
+    def test_fk_mismatched_local_remote_cols(self):
+
+        assert_raises_message(
+            exc.ArgumentError,
+            "ForeignKeyConstraint number of constrained columns must "
+            "match the number of referenced columns.",
+            ForeignKeyConstraint, ['a'], ['b.a', 'b.b']
+        )
+
+        assert_raises_message(
+            exc.ArgumentError,
+            "ForeignKeyConstraint number of constrained columns "
+            "must match the number of referenced columns.",
+            ForeignKeyConstraint, ['a', 'b'], ['b.a']
+        )
+
+        assert_raises_message(
+            exc.ArgumentError,
+            "ForeignKeyConstraint with duplicate source column "
+            "references are not supported.",
+            ForeignKeyConstraint, ['a', 'a'], ['b.a', 'b.b']
+        )
+
     def test_pickle_metadata_sequence_restated(self):
         m1 = MetaData()
         Table('a', m1,