You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by el...@apache.org on 2026/08/07 15:11:25 UTC
(superset) branch fix-adhoc-column-error-mislabel created (now 349a7f639df)
This is an automated email from the ASF dual-hosted git repository.
eschutho pushed a change to branch fix-adhoc-column-error-mislabel
in repository https://gitbox.apache.org/repos/asf/superset.git
at 349a7f639df fix(sqla): don't mislabel DB errors as ColumnNotFoundException in adhoc_column_to_sqla (SC-116904)
This branch includes the following new commits:
new 349a7f639df fix(sqla): don't mislabel DB errors as ColumnNotFoundException in adhoc_column_to_sqla (SC-116904)
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(superset) 01/01: fix(sqla): don't mislabel DB errors as ColumnNotFoundException in adhoc_column_to_sqla (SC-116904)
Posted by el...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
eschutho pushed a commit to branch fix-adhoc-column-error-mislabel
in repository https://gitbox.apache.org/repos/asf/superset.git
commit 349a7f639dfb353669c001187706d7fd0112ed2f
Author: Elizabeth Thompson <es...@gmail.com>
AuthorDate: Fri Aug 7 15:11:13 2026 +0000
fix(sqla): don't mislabel DB errors as ColumnNotFoundException in adhoc_column_to_sqla (SC-116904)
The type-probe in SqlaTable.adhoc_column_to_sqla wrapped the whole probe in
a try/except SupersetGenericDBErrorException that relabeled every such error
as ColumnNotFoundException. Because get_columns_description wraps any
execution failure (SSL EOF, timeouts, dropped connections) as a
SupersetGenericDBErrorException, real transient DB errors were mislabeled as
"column not found". where_clause's adhoc-filter handling then silently dropped
the filter instead of surfacing the DB error.
Only the genuine empty-result case now raises ColumnNotFoundException; a real
DB/connectivity failure from get_columns_description propagates unchanged.
Fixes SUPERSET-PYTHON-WE6
Co-Authored-By: Claude Opus 4.8 <no...@anthropic.com>
---
superset/connectors/sqla/models.py | 74 ++++++++++++++++-----------------
tests/unit_tests/models/helpers_test.py | 45 ++++++++++++++++++++
2 files changed, 82 insertions(+), 37 deletions(-)
diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py
index 72a19a5b434..f750d8802c7 100644
--- a/superset/connectors/sqla/models.py
+++ b/superset/connectors/sqla/models.py
@@ -84,7 +84,6 @@ from superset.exceptions import (
ColumnNotFoundException,
DatasetInvalidPermissionEvaluationException,
QueryObjectValidationError,
- SupersetGenericDBErrorException,
SupersetParseError,
SupersetSecurityException,
SupersetSyntaxErrorException,
@@ -1858,42 +1857,43 @@ class SqlaTable(
sqla_column = literal_column(expression)
if has_timegrain or force_type_check:
- try:
- # probe adhoc column type
- # Most databases populate cursor.description from query-plan
- # metadata, so WHERE FALSE (zero rows, no table scan) is
- # preferred — it avoids hitting row-read limits enforced by
- # engines like ClickHouse (max_rows_to_read).
- # A small number of drivers (Druid, Pinot) instead build
- # cursor.description by inspecting the first returned row;
- # for those we fall back to LIMIT 1.
- tbl, _unused_cte = self.get_from_clause(template_processor)
- if self.db_engine_spec.type_probe_needs_row:
- qry = sa.select(sqla_column).limit(1).select_from(tbl)
- else:
- qry = sa.select(sqla_column).where(sa.false()).select_from(tbl)
- sql = self.database.compile_sqla_query(
- qry,
- catalog=self.catalog,
- schema=self.schema,
- )
- col_desc = get_columns_description(
- self.database,
- self.catalog,
- self.schema or None,
- sql,
- )
- if not col_desc:
- raise SupersetGenericDBErrorException("Column not found")
- is_dttm = col_desc[0]["is_dttm"] # type: ignore
- # ResultSet already resolves the generic type from the
- # driver's cursor.description; reuse it so callers can
- # coerce filter values correctly (e.g. numeric IN-lists
- # stay unquoted for numeric adhoc expressions like
- # CAST(... AS BIGINT)).
- generic_type = col_desc[0].get("type_generic")
- except SupersetGenericDBErrorException as ex:
- raise ColumnNotFoundException(message=str(ex)) from ex
+ # probe adhoc column type
+ # Most databases populate cursor.description from query-plan
+ # metadata, so WHERE FALSE (zero rows, no table scan) is
+ # preferred — it avoids hitting row-read limits enforced by
+ # engines like ClickHouse (max_rows_to_read).
+ # A small number of drivers (Druid, Pinot) instead build
+ # cursor.description by inspecting the first returned row;
+ # for those we fall back to LIMIT 1.
+ tbl, _unused_cte = self.get_from_clause(template_processor)
+ if self.db_engine_spec.type_probe_needs_row:
+ qry = sa.select(sqla_column).limit(1).select_from(tbl)
+ else:
+ qry = sa.select(sqla_column).where(sa.false()).select_from(tbl)
+ sql = self.database.compile_sqla_query(
+ qry,
+ catalog=self.catalog,
+ schema=self.schema,
+ )
+ # A real DB/connectivity failure during the probe surfaces as a
+ # SupersetGenericDBErrorException from get_columns_description and
+ # is allowed to propagate unchanged; only a genuine empty result
+ # (the column truly isn't there) is a ColumnNotFoundException.
+ col_desc = get_columns_description(
+ self.database,
+ self.catalog,
+ self.schema or None,
+ sql,
+ )
+ if not col_desc:
+ raise ColumnNotFoundException(message="Column not found")
+ is_dttm = col_desc[0]["is_dttm"] # type: ignore
+ # ResultSet already resolves the generic type from the
+ # driver's cursor.description; reuse it so callers can
+ # coerce filter values correctly (e.g. numeric IN-lists
+ # stay unquoted for numeric adhoc expressions like
+ # CAST(... AS BIGINT)).
+ generic_type = col_desc[0].get("type_generic")
if is_dttm and has_timegrain:
sqla_column = self.db_engine_spec.get_timestamp_expr(
diff --git a/tests/unit_tests/models/helpers_test.py b/tests/unit_tests/models/helpers_test.py
index 747527536f7..49f122b60d2 100644
--- a/tests/unit_tests/models/helpers_test.py
+++ b/tests/unit_tests/models/helpers_test.py
@@ -2899,6 +2899,51 @@ def test_adhoc_column_type_probe_uses_where_false(database: Database) -> None:
)
+def test_adhoc_column_type_probe_propagates_db_error(database: Database) -> None:
+ """
+ Regression test for SUPERSET-PYTHON-WE6.
+
+ A real DB/connectivity failure during the type-probe (surfaced by
+ get_columns_description as a SupersetGenericDBErrorException) must propagate
+ unchanged. It must NOT be relabeled as ColumnNotFoundException, which would
+ cause the calling adhoc-filter code in models/helpers.py to silently drop
+ the filter as if the column didn't exist.
+ """
+ from unittest.mock import patch
+
+ from superset.connectors.sqla.models import SqlaTable, TableColumn
+ from superset.exceptions import (
+ ColumnNotFoundException,
+ SupersetGenericDBErrorException,
+ )
+
+ table = SqlaTable(
+ database=database,
+ schema=None,
+ table_name="t",
+ columns=[TableColumn(column_name="a", type="INTEGER")],
+ )
+
+ adhoc_col: AdhocColumn = {
+ "sqlExpression": "round(a / 50) * 50 / 1000",
+ "label": "Duration",
+ "columnType": "BASE_AXIS",
+ "timeGrain": "P1D",
+ }
+
+ db_error_message = "SSL error: unexpected eof while reading"
+
+ with patch(
+ "superset.connectors.sqla.models.get_columns_description",
+ side_effect=SupersetGenericDBErrorException(db_error_message),
+ ):
+ with pytest.raises(SupersetGenericDBErrorException) as excinfo:
+ table.adhoc_column_to_sqla(adhoc_col, force_type_check=True)
+
+ assert db_error_message in str(excinfo.value)
+ assert not isinstance(excinfo.value, ColumnNotFoundException)
+
+
def test_adhoc_column_type_probe_uses_limit_1_for_row_dependent_engines(
database: Database,
) -> None: