From daf209bd6b6fe94cbec162cef2a53c49e9f31cde Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Fri, 3 Jun 2016 15:07:14 -0400 Subject: [PATCH] Ensure 'options' is always present in foreign key info Regarding 0e88bcc30ed49193b91f248123f526fa30007f22, "options" needs to be present as a key in the dictionary because Alembic uses this as a guide to know if the backend is even capable of reporting on foreign key options. Change-Id: I271090f75088cfeec24315a878060f9b8a265335 --- lib/sqlalchemy/dialects/sqlite/base.py | 3 +- lib/sqlalchemy/testing/requirements.py | 4 ++ .../testing/suite/test_reflection.py | 51 +++++++++++++++++++ test/requirements.py | 3 ++ 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/lib/sqlalchemy/dialects/sqlite/base.py b/lib/sqlalchemy/dialects/sqlite/base.py index 85ebaaa1d0..7ddd099935 100644 --- a/lib/sqlalchemy/dialects/sqlite/base.py +++ b/lib/sqlalchemy/dialects/sqlite/base.py @@ -1420,8 +1420,7 @@ class SQLiteDialect(default.DefaultDialect): continue key = keys_by_signature.pop(sig) key['name'] = constraint_name - if options: - key['options'] = options + key['options'] = options fkeys.append(key) # assume the remainders are the unnamed, inline constraints, just # use them as is as it's extremely difficult to parse inline diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py index d4c0dff8fb..a9370a30e2 100644 --- a/lib/sqlalchemy/testing/requirements.py +++ b/lib/sqlalchemy/testing/requirements.py @@ -339,6 +339,10 @@ class SuiteRequirements(Requirements): def foreign_key_constraint_reflection(self): return exclusions.open() + @property + def foreign_key_constraint_option_reflection(self): + return exclusions.closed() + @property def temp_table_reflection(self): return exclusions.open() diff --git a/lib/sqlalchemy/testing/suite/test_reflection.py b/lib/sqlalchemy/testing/suite/test_reflection.py index 1874f6210e..dced2e3457 100644 --- a/lib/sqlalchemy/testing/suite/test_reflection.py +++ b/lib/sqlalchemy/testing/suite/test_reflection.py @@ -472,6 +472,57 @@ class ComponentReflectionTest(fixtures.TablesTest): def test_get_foreign_keys_with_schema(self): self._test_get_foreign_keys(schema=testing.config.test_schema) + @testing.requires.foreign_key_constraint_option_reflection + @testing.provide_metadata + def test_get_foreign_key_options(self): + meta = self.metadata + + Table( + 'x', meta, + Column('id', Integer, primary_key=True), + ) + + Table('table', meta, + Column('id', Integer, primary_key=True), + Column('x_id', Integer, sa.ForeignKey('x.id', name='xid')), + Column('test', String(10)), + test_needs_fk=True) + + Table('user', meta, + Column('id', Integer, primary_key=True), + Column('name', String(50), nullable=False), + Column('tid', Integer), + sa.ForeignKeyConstraint( + ['tid'], ['table.id'], + name='myfk', + onupdate="SET NULL", ondelete="CASCADE"), + test_needs_fk=True) + + meta.create_all() + + insp = inspect(meta.bind) + + # test 'options' is always present for a backend + # that can reflect these, since alembic looks for this + opts = insp.get_foreign_keys('table')[0]['options'] + + eq_( + dict( + (k, opts[k]) + for k in opts if opts[k] + ), + {} + ) + + opts = insp.get_foreign_keys('user')[0]['options'] + eq_( + dict( + (k, opts[k]) + for k in opts if opts[k] + ), + {'onupdate': 'SET NULL', 'ondelete': 'CASCADE'} + ) + @testing.provide_metadata def _test_get_indexes(self, schema=None): meta = self.metadata diff --git a/test/requirements.py b/test/requirements.py index 0609b3cbf1..554e5296fe 100644 --- a/test/requirements.py +++ b/test/requirements.py @@ -84,6 +84,9 @@ class DefaultRequirements(SuiteRequirements): return only_on(['oracle']) + @property + def foreign_key_constraint_option_reflection(self): + return only_on(['postgresql', 'mysql', 'sqlite']) @property def unbounded_varchar(self): -- 2.47.2