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()