]> 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 19:57:03 +0000 (15:57 -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

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

index 89d42f4732124ee1cc59194b09fd8c89ec0f78f7..66f6bb45ade286297e2c6e2da851afc08dfcb952 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 a9aee58835439c0870568e454adc266b5e2d2084..4eb095196c7ca4acbe0082988d289053611956ac 100644 (file)
@@ -2883,6 +2883,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,