This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new defcd639107 fix(mongodb): route dataset FROM clauses through
quote_table so chart queries return rows (#44583)
defcd639107 is described below
commit defcd639107946734c068bc33c3c32ce71ee2e5d
Author: akashchamp <[email protected]>
AuthorDate: Fri Sep 25 02:46:12 2026 +0530
fix(mongodb): route dataset FROM clauses through quote_table so chart
queries return rows (#44583)
Co-authored-by: Claude Sonnet 5 <[email protected]>
---
superset/connectors/sqla/models.py | 14 ++++-
superset/db_engine_specs/base.py | 10 ++++
superset/db_engine_specs/mongodb.py | 9 ++++
tests/unit_tests/connectors/sqla/models_test.py | 68 ++++++++++++++++++++++++
tests/unit_tests/db_engine_specs/test_mongodb.py | 36 +++++++++++++
5 files changed, 136 insertions(+), 1 deletion(-)
diff --git a/superset/connectors/sqla/models.py
b/superset/connectors/sqla/models.py
index 7bb0b48855a..548a5403660 100644
--- a/superset/connectors/sqla/models.py
+++ b/superset/connectors/sqla/models.py
@@ -1985,7 +1985,19 @@ class SqlaTable(
return table(quoted_name(full_name, quote=False))
if self.schema:
- return table(self.table_name, schema=self.schema)
+ if self.database.db_engine_spec.quote_table_includes_schema:
+ return table(self.table_name, schema=self.schema)
+
+ # This engine's `quote_table` doesn't qualify the identifier with
the
+ # schema (e.g. MongoDB/PyMongoSQL, which takes the whole FROM
reference
+ # as a literal collection name). Build the FROM-clause identifier
the
+ # same way `select_star` does for SQL Lab, and rely on
+ # `adjust_engine_params` to select the schema at the connection
level.
+ full_table_name = self.database.db_engine_spec.quote_table(
+ Table(self.table_name, self.schema),
+ self.database.get_dialect(),
+ )
+ return table(quoted_name(full_table_name, quote=False))
return table(self.table_name)
diff --git a/superset/db_engine_specs/base.py b/superset/db_engine_specs/base.py
index a32aeb9f34d..3989fbc42e8 100644
--- a/superset/db_engine_specs/base.py
+++ b/superset/db_engine_specs/base.py
@@ -682,6 +682,16 @@ class BaseEngineSpec: # pylint:
disable=too-many-public-methods
# a custom `adjust_engine_params` method.
supports_dynamic_schema = False
+ # Does the qualified identifier built by `quote_table` include the schema
(and
+ # catalog, if any)? True for virtually every engine. A driver that treats
the
+ # whole FROM reference as a single opaque name (e.g. PyMongoSQL, which
resolves
+ # `schema.table` as a literal collection name instead of parsing it) sets
this to
+ # False and overrides `quote_table` to emit only the table, relying on
+ # `adjust_engine_params`/`supports_dynamic_schema` to select the schema at
the
+ # connection level instead. `SqlaTable.get_sqla_table` consults this flag
so
+ # datasets build the same FROM-clause identifier as `select_star` (SQL
Lab).
+ quote_table_includes_schema = True
+
# Does the DB support catalogs? A catalog here is a group of schemas, and
has
# different names depending on the DB: BigQuery calles it a "project",
Postgres calls # noqa: E501
# it a "database", Trino calls it a "catalog", etc.
diff --git a/superset/db_engine_specs/mongodb.py
b/superset/db_engine_specs/mongodb.py
index 28c41759525..51013918e3e 100644
--- a/superset/db_engine_specs/mongodb.py
+++ b/superset/db_engine_specs/mongodb.py
@@ -52,6 +52,15 @@ class MongoDBEngineSpec(BaseEngineSpec):
# the default ``authSource``.
supports_dynamic_schema = True
+ # `quote_table` below emits only the bare, quoted collection name --
PyMongoSQL
+ # resolves the whole FROM reference as a literal collection name, so a
+ # schema-qualified identifier would never match. `SqlaTable.get_sqla_table`
+ # (charts) consults this flag; `select_star` (SQL Lab) reaches the same
+ # unqualified FROM clause through the `quote_table` override above. Both
+ # rely on `adjust_engine_params` to select the schema at the connection
+ # level instead.
+ quote_table_includes_schema = False
+
metadata = {
"description": ("MongoDB is a document-oriented, operational NoSQL
database."),
"logo": "mongodb.png",
diff --git a/tests/unit_tests/connectors/sqla/models_test.py
b/tests/unit_tests/connectors/sqla/models_test.py
index 41212930f9e..ee05de12a3d 100644
--- a/tests/unit_tests/connectors/sqla/models_test.py
+++ b/tests/unit_tests/connectors/sqla/models_test.py
@@ -1408,6 +1408,74 @@ def test_quoted_name_prevents_double_quoting(mocker:
MockerFixture) -> None:
assert '"MY_DB"."MY_SCHEMA"."MY_TABLE"' in compiled
+def test_get_sqla_table_schema_not_qualified_when_engine_opts_out(
+ mocker: MockerFixture,
+) -> None:
+ """
+ Engines that set ``quote_table_includes_schema = False`` (e.g. MongoDB,
whose
+ PyMongoSQL driver resolves ``schema.collection`` as a literal collection
name
+ instead of parsing it) must get an unqualified FROM-clause identifier from
+ ``get_sqla_table``, built through the engine spec's own ``quote_table``,
the
+ same way ``select_star`` builds it for SQL Lab's Data Preview. Regression
test
+ for datasets on such engines returning no rows once a schema is set.
+ """
+ from sqlalchemy import create_engine, select
+
+ engine = create_engine("sqlite://")
+
+ database = mocker.MagicMock()
+ database.db_engine_spec.supports_cross_catalog_queries = False
+ database.db_engine_spec.quote_table_includes_schema = False
+ database.db_engine_spec.quote_table.side_effect = (
+ lambda table, dialect: dialect.identifier_preparer.quote(table.table)
+ )
+ database.get_dialect.return_value = engine.dialect
+
+ table = SqlaTable(
+ table_name="orders",
+ database=database,
+ schema="testdb",
+ )
+
+ sqla_table = table.get_sqla_table()
+ compiled = str(
+ select(sqla_table).compile(engine, compile_kwargs={"literal_binds":
True})
+ )
+
+ assert "FROM orders" in compiled
+ assert "testdb" not in compiled
+ database.db_engine_spec.quote_table.assert_called_once()
+
+
+def test_get_sqla_table_schema_qualified_by_default(mocker: MockerFixture) ->
None:
+ """
+ Engines that don't override ``quote_table_includes_schema`` (the default,
+ ``True``) keep qualifying the FROM clause with the schema, unaffected by
the
+ opt-out path above.
+ """
+ from sqlalchemy import create_engine, select
+
+ engine = create_engine("postgresql://user:pass@host/db")
+
+ database = mocker.MagicMock()
+ database.db_engine_spec.supports_cross_catalog_queries = False
+ database.db_engine_spec.quote_table_includes_schema = True
+
+ table = SqlaTable(
+ table_name="My-Table",
+ database=database,
+ schema="My-Schema",
+ )
+
+ sqla_table = table.get_sqla_table()
+ compiled = str(
+ select(sqla_table).compile(engine, compile_kwargs={"literal_binds":
True})
+ )
+
+ assert '"My-Schema"."My-Table"' in compiled
+ database.db_engine_spec.quote_table.assert_not_called()
+
+
def test_sqla_table_currency_code_column_property() -> None:
"""
Test currency_code_column property on SqlaTable.
diff --git a/tests/unit_tests/db_engine_specs/test_mongodb.py
b/tests/unit_tests/db_engine_specs/test_mongodb.py
index 8a481daa082..c8cb94d463e 100644
--- a/tests/unit_tests/db_engine_specs/test_mongodb.py
+++ b/tests/unit_tests/db_engine_specs/test_mongodb.py
@@ -326,3 +326,39 @@ def test_get_sqla_engine_applies_selected_schema() -> None:
assert credentials.source == "dbone"
finally:
raw_connection.close()
+
+
+def test_get_sqla_table_does_not_qualify_collection() -> None:
+ """
+ Charts built on a MongoDB dataset with a schema hit the same PyMongoSQL
+ ``schema.collection`` resolution bug SQL Lab's Data Preview had before
#44141:
+ ``SqlaTable.get_sqla_table`` must build an unqualified FROM clause too, not
+ just ``select_star``. Regression test for #44576.
+ """
+ from sqlalchemy import create_engine, select
+
+ from superset.connectors.sqla.models import SqlaTable
+ from superset.db_engine_specs.mongodb import MongoDBEngineSpec
+ from superset.models.core import Database
+
+ pytest.importorskip("pymongosql")
+
+ assert MongoDBEngineSpec.quote_table_includes_schema is False
+
+ database = Database(
+ database_name="mongo",
+ sqlalchemy_uri="mongodb://user:pass@host:27017/dbone?mode=superset",
+ )
+ dataset = SqlaTable(table_name="orders", database=database,
schema="testdb")
+
+ sqla_table = dataset.get_sqla_table()
+
+ # Compile with the engine's own dialect -- the one `get_sqla_table` already
+ # quoted the identifier with -- so the assertion covers the real path.
+ engine = create_engine(database.sqlalchemy_uri)
+ compiled = str(
+ select(sqla_table).compile(engine, compile_kwargs={"literal_binds":
True})
+ )
+
+ assert "FROM orders" in compiled
+ assert "testdb" not in compiled