summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/build/changelog/unreleased_14/3247.rst9
-rw-r--r--lib/sqlalchemy/engine/reflection.py5
-rw-r--r--lib/sqlalchemy/testing/requirements.py8
-rw-r--r--lib/sqlalchemy/testing/suite/test_reflection.py56
-rw-r--r--test/requirements.py5
5 files changed, 81 insertions, 2 deletions
diff --git a/doc/build/changelog/unreleased_14/3247.rst b/doc/build/changelog/unreleased_14/3247.rst
new file mode 100644
index 000000000..4ddb457f7
--- /dev/null
+++ b/doc/build/changelog/unreleased_14/3247.rst
@@ -0,0 +1,9 @@
+.. change::
+ :tags: engine, bug, postgresql
+ :tickets: 3247
+
+ The :meth:`_engine.Inspector.reflect_table` method now supports reflecting
+ tables that do not have user defined columns. This allows
+ :meth:`_schema.MetaData.reflect` to properly complete reflection on
+ databases that contain such tables. Currently, only PostgreSQL is known
+ to support such a construct among the common database backends.
diff --git a/lib/sqlalchemy/engine/reflection.py b/lib/sqlalchemy/engine/reflection.py
index 715781b7a..113aa8ea0 100644
--- a/lib/sqlalchemy/engine/reflection.py
+++ b/lib/sqlalchemy/engine/reflection.py
@@ -784,8 +784,9 @@ class Inspector(object):
cols_by_orig_name,
)
- if not found_table:
- raise exc.NoSuchTableError(table.name)
+ # NOTE: support tables/views with no columns
+ if not found_table and not self.has_table(table_name, schema):
+ raise exc.NoSuchTableError(table_name)
self._reflect_pk(
table_name, schema, table, cols_by_orig_name, exclude_columns
diff --git a/lib/sqlalchemy/testing/requirements.py b/lib/sqlalchemy/testing/requirements.py
index a546e1feb..f8b5dd606 100644
--- a/lib/sqlalchemy/testing/requirements.py
+++ b/lib/sqlalchemy/testing/requirements.py
@@ -589,9 +589,17 @@ class SuiteRequirements(Requirements):
@property
def table_reflection(self):
+ """target database has general support for table reflection"""
return exclusions.open()
@property
+ def reflect_tables_no_columns(self):
+ """target database supports creation and reflection of tables with no
+ columns, or at least tables that seem to have no columns."""
+
+ return exclusions.closed()
+
+ @property
def comment_reflection(self):
return exclusions.closed()
diff --git a/lib/sqlalchemy/testing/suite/test_reflection.py b/lib/sqlalchemy/testing/suite/test_reflection.py
index 916d74db3..88189c2d9 100644
--- a/lib/sqlalchemy/testing/suite/test_reflection.py
+++ b/lib/sqlalchemy/testing/suite/test_reflection.py
@@ -1063,6 +1063,61 @@ class ComponentReflectionTest(fixtures.TablesTest):
assert id_.get("autoincrement", True)
+class TableNoColumnsTest(fixtures.TestBase):
+ __requires__ = ("reflect_tables_no_columns",)
+ __backend__ = True
+
+ @testing.fixture
+ def table_no_columns(self, connection, metadata):
+ Table("empty", metadata)
+ metadata.create_all(connection)
+
+ @testing.fixture
+ def view_no_columns(self, connection, metadata):
+ Table("empty", metadata)
+ metadata.create_all(connection)
+
+ Table("empty", metadata)
+ event.listen(
+ metadata,
+ "after_create",
+ DDL("CREATE VIEW empty_v AS SELECT * FROM empty"),
+ )
+
+ # for transactional DDL the transaction is rolled back before this
+ # drop statement is invoked
+ event.listen(
+ metadata, "before_drop", DDL("DROP VIEW IF EXISTS empty_v")
+ )
+ metadata.create_all(connection)
+
+ @testing.requires.reflect_tables_no_columns
+ def test_reflect_table_no_columns(self, connection, table_no_columns):
+ t2 = Table("empty", MetaData(), autoload_with=connection)
+ eq_(list(t2.c), [])
+
+ @testing.requires.reflect_tables_no_columns
+ def test_get_columns_table_no_columns(self, connection, table_no_columns):
+ eq_(inspect(connection).get_columns("empty"), [])
+
+ @testing.requires.reflect_tables_no_columns
+ def test_reflect_incl_table_no_columns(self, connection, table_no_columns):
+ m = MetaData()
+ m.reflect(connection)
+ assert set(m.tables).intersection(["empty"])
+
+ @testing.requires.views
+ @testing.requires.reflect_tables_no_columns
+ def test_reflect_view_no_columns(self, connection, view_no_columns):
+ t2 = Table("empty_v", MetaData(), autoload_with=connection)
+ eq_(list(t2.c), [])
+
+ @testing.requires.views
+ @testing.requires.reflect_tables_no_columns
+ def test_get_columns_view_no_columns(self, connection, view_no_columns):
+ eq_(inspect(connection).get_columns("empty_v"), [])
+
+
class ComponentReflectionTestExtra(fixtures.TestBase):
__backend__ = True
@@ -1641,6 +1696,7 @@ class CompositeKeyReflectionTest(fixtures.TablesTest):
__all__ = (
"ComponentReflectionTest",
"ComponentReflectionTestExtra",
+ "TableNoColumnsTest",
"QuotedNameArgumentTest",
"HasTableTest",
"HasIndexTest",
diff --git a/test/requirements.py b/test/requirements.py
index 721bb8ba3..7efd6cbd5 100644
--- a/test/requirements.py
+++ b/test/requirements.py
@@ -1805,3 +1805,8 @@ class DefaultRequirements(SuiteRequirements):
@property
def autoincrement_without_sequence(self):
return skip_if("oracle")
+
+ @property
+ def reflect_tables_no_columns(self):
+ # so far sqlite, mariadb, mysql don't support this
+ return only_on(["postgresql"])