diff --git a/superset/db_engine_specs/postgres.py b/superset/db_engine_specs/postgres.py index 1945fa3f303..406e85effe2 100644 --- a/superset/db_engine_specs/postgres.py +++ b/superset/db_engine_specs/postgres.py @@ -821,6 +821,32 @@ WHERE datistemplate = false; ) } + @classmethod + def get_schema_names(cls, inspector: Inspector) -> set[str]: + """ + Return all schema names, excluding the ``pg_``-prefixed Postgres + system schemas (e.g. ``pg_catalog``, ``pg_toast``). + + SQLAlchemy's Postgres dialect filters out system schemas with the + query ``nspname NOT LIKE 'pg_%'``. Since ``_`` is a single-character + wildcard in SQL ``LIKE`` patterns, this unintentionally excludes any + user-defined schema that merely starts with ``pg`` followed by any + other character (e.g. ``pgsql``, ``pgstats``), not only the + ``pg_``-prefixed system schemas. Matching on the literal ``pg_`` + prefix instead keeps those user-defined schemas. + + TODO: drop this override once sqlalchemy/sqlalchemy#13471 is merged + and released, and SQLAlchemy is bumped past that version. + """ + with inspector.engine.connect() as conn: + return { + name + for (name,) in conn.execute( + text("SELECT nspname FROM pg_namespace ORDER BY nspname") + ) + if not name.startswith("pg_") + } + @classmethod def get_table_names( cls, database: Database, inspector: PGInspector, schema: str | None diff --git a/tests/unit_tests/db_engine_specs/test_postgres.py b/tests/unit_tests/db_engine_specs/test_postgres.py index 287fcec38a3..c1840d9fd71 100644 --- a/tests/unit_tests/db_engine_specs/test_postgres.py +++ b/tests/unit_tests/db_engine_specs/test_postgres.py @@ -450,3 +450,33 @@ def test_interval_type_mutator() -> None: assert mutator(True) is None assert mutator([1, 2, 3]) is None assert mutator({"days": 1}) is None + + +def test_get_schema_names_excludes_only_actual_system_schemas( + mocker: MockerFixture, +) -> None: + """ + DB Eng Specs (postgres): Test ``get_schema_names`` + + User-defined schemas that merely start with ``pg`` (but are not + actual Postgres system schemas, which always start with the literal + ``pg_``) must not be filtered out. See issue #30678. + """ + inspector = mocker.MagicMock() + inspector.engine.connect().__enter__().execute.return_value = [ + ("public",), + ("pgsql",), + ("pgstats",), + ("pg_catalog",), + ("pg_toast",), + ("information_schema",), + ] + + schemas = spec.get_schema_names(inspector) + + assert schemas == { + "public", + "pgsql", + "pgstats", + "information_schema", + }