You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jl...@apache.org on 2026/08/13 20:16:23 UTC
(superset) branch master updated: chore(duckdb): remove cursor.description workaround, no longer reproducible (#43101)
This is an automated email from the ASF dual-hosted git repository.
sadpandajoe 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 3fb58900b9a chore(duckdb): remove cursor.description workaround, no longer reproducible (#43101)
3fb58900b9a is described below
commit 3fb58900b9a6c310e17fc73a2ff5cec93a9776d8
Author: Evan Rusackas <ev...@preset.io>
AuthorDate: Thu Aug 13 13:16:16 2026 -0700
chore(duckdb): remove cursor.description workaround, no longer reproducible (#43101)
Co-authored-by: Superset Dev <de...@superset.apache.org>
Co-authored-by: Claude Sonnet 5 <no...@anthropic.com>
Co-authored-by: Joe Li <jo...@preset.io>
---
superset/db_engine_specs/duckdb.py | 35 +------------------------
tests/unit_tests/db_engine_specs/test_duckdb.py | 28 ++++++++++++++++++++
2 files changed, 29 insertions(+), 34 deletions(-)
diff --git a/superset/db_engine_specs/duckdb.py b/superset/db_engine_specs/duckdb.py
index a123ac6fe67..87e02c37761 100644
--- a/superset/db_engine_specs/duckdb.py
+++ b/superset/db_engine_specs/duckdb.py
@@ -33,7 +33,7 @@ from sqlalchemy.engine.url import URL
from superset.constants import TimeGrain
from superset.databases.utils import make_url_safe
-from superset.db_engine_specs.base import BaseEngineSpec, DatabaseCategory, LimitMethod
+from superset.db_engine_specs.base import BaseEngineSpec, DatabaseCategory
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
from superset.utils.core import GenericDataType, get_user_agent, QuerySource
@@ -313,39 +313,6 @@ class DuckDBEngineSpec(DuckDBParametersMixin, BaseEngineSpec):
return f"""'{dttm.isoformat(sep=" ", timespec="microseconds")}'"""
return None
- @classmethod
- def fetch_data(cls, cursor: Any, limit: int | None = None) -> list[tuple[Any, ...]]:
- """
- Override fetch_data to work around duckdb-engine cursor.description bug.
-
- The duckdb-engine SQLAlchemy driver has a bug where cursor.description
- becomes None after calling fetchall(), even though the native DuckDB cursor
- preserves this information correctly.
-
- See: https://github.com/Mause/duckdb_engine/issues/1322
-
- This method captures the cursor description before fetchall() and restores
- it afterward to prevent downstream processing failures.
- """
- # Capture description BEFORE fetchall() invalidates it
- description = cursor.description
-
- # Execute fetchall() (which will clear cursor.description in duckdb-engine)
- if cls.arraysize:
- cursor.arraysize = cls.arraysize
- try:
- if cls.limit_method == LimitMethod.FETCH_MANY and limit:
- data = cursor.fetchmany(limit)
- else:
- data = cursor.fetchall()
- except Exception as ex:
- raise cls.get_dbapi_mapped_exception(ex) from ex
-
- # Restore the captured description for downstream processing
- cursor.description = description
-
- return data
-
@classmethod
def get_table_names(
cls, database: Database, inspector: Inspector, schema: str | None
diff --git a/tests/unit_tests/db_engine_specs/test_duckdb.py b/tests/unit_tests/db_engine_specs/test_duckdb.py
index 738bc71bd26..5ad991a3b2c 100644
--- a/tests/unit_tests/db_engine_specs/test_duckdb.py
+++ b/tests/unit_tests/db_engine_specs/test_duckdb.py
@@ -163,3 +163,31 @@ def test_column_type_recognition() -> None:
col_spec = DuckDBEngineSpec.get_column_spec("TINYINT")
# TINYINT matches the pattern "^int" so it should be recognized
assert col_spec is None, "TINYINT doesn't match any patterns"
+
+
+def test_fetch_data_preserves_cursor_description(mocker: MockerFixture) -> None:
+ """
+ DuckDBEngineSpec previously overrode fetch_data to capture and restore
+ cursor.description around fetchall(), working around a duckdb-engine bug
+ that no longer reproduces at current pinned versions (duckdb-engine
+ 0.17.0, duckdb 1.5.5). Confirm the inherited base fetch_data still works
+ correctly against a real cursor now that the override is gone.
+ """
+ from sqlalchemy import create_engine
+
+ from superset.db_engine_specs.duckdb import DuckDBEngineSpec
+
+ engine = create_engine("duckdb:///:memory:")
+ raw_conn = engine.raw_connection()
+ try:
+ cursor = raw_conn.cursor()
+ database = mocker.MagicMock()
+ DuckDBEngineSpec.execute(cursor, "SELECT 1 AS col1, 2 AS col2", database)
+
+ data = DuckDBEngineSpec.fetch_data(cursor)
+
+ assert data == [(1, 2)]
+ assert cursor.description is not None
+ assert [col[0] for col in cursor.description] == ["col1", "col2"]
+ finally:
+ raw_conn.close()