You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by dp...@apache.org on 2026/04/28 15:52:55 UTC

(superset) branch danielgaspar/sc-103472/sqllab-mysql-bug-when-using created (now 50cfa5c48db)

This is an automated email from the ASF dual-hosted git repository.

dpgaspar pushed a change to branch danielgaspar/sc-103472/sqllab-mysql-bug-when-using
in repository https://gitbox.apache.org/repos/asf/superset.git


      at 50cfa5c48db fix(mysql): fallback to pymysql when MySQLdb is not installed in get_datatype()

This branch includes the following new commits:

     new 50cfa5c48db fix(mysql): fallback to pymysql when MySQLdb is not installed in get_datatype()

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(mysql): fallback to pymysql when MySQLdb is not installed in get_datatype()

Posted by dp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dpgaspar pushed a commit to branch danielgaspar/sc-103472/sqllab-mysql-bug-when-using
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 50cfa5c48db5bb66d49fdf487e3e2cc6b69ac9b3
Author: Daniel Gaspar <da...@gmail.com>
AuthorDate: Tue Apr 28 16:52:24 2026 +0100

    fix(mysql): fallback to pymysql when MySQLdb is not installed in get_datatype()
    
    MySQLEngineSpec.get_datatype() hardcoded `import MySQLdb` which fails
    in environments using pymysql driver (e.g., Alpine-based images).
    Added try/except to fall back to pymysql when MySQLdb is unavailable.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <no...@anthropic.com>
---
 superset/db_engine_specs/mysql.py              |  5 +++-
 tests/unit_tests/db_engine_specs/test_mysql.py | 41 ++++++++++++++++++++++++++
 2 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/superset/db_engine_specs/mysql.py b/superset/db_engine_specs/mysql.py
index 99bf203f9a4..21514ebbf00 100644
--- a/superset/db_engine_specs/mysql.py
+++ b/superset/db_engine_specs/mysql.py
@@ -402,7 +402,10 @@ class MySQLEngineSpec(BasicParametersMixin, BaseEngineSpec):
         if not cls.type_code_map:
             # only import and store if needed at least once
             # pylint: disable=import-outside-toplevel
-            import MySQLdb
+            try:
+                import MySQLdb
+            except ImportError:
+                import pymysql as MySQLdb
 
             ft = MySQLdb.constants.FIELD_TYPE
             cls.type_code_map = {
diff --git a/tests/unit_tests/db_engine_specs/test_mysql.py b/tests/unit_tests/db_engine_specs/test_mysql.py
index 649af5c7c15..7980fdfed03 100644
--- a/tests/unit_tests/db_engine_specs/test_mysql.py
+++ b/tests/unit_tests/db_engine_specs/test_mysql.py
@@ -18,6 +18,8 @@
 from datetime import datetime
 from decimal import Decimal
 from typing import Any, Optional
+import builtins
+from types import ModuleType
 from unittest.mock import Mock, patch
 
 import pytest
@@ -262,3 +264,42 @@ def test_column_type_mutator(
     mock_cursor.description = description
 
     assert spec.fetch_data(mock_cursor) == expected_result
+
+
+def test_get_datatype_pymysql_fallback():
+    """get_datatype() falls back to pymysql when MySQLdb is not installed."""
+    from superset.db_engine_specs.mysql import MySQLEngineSpec
+
+    # Reset cached type_code_map so the import path is exercised
+    original_type_code_map = MySQLEngineSpec.type_code_map
+    MySQLEngineSpec.type_code_map = {}
+
+    try:
+        # Build a fake pymysql module with constants.FIELD_TYPE
+        fake_field_type = ModuleType("pymysql.constants.FIELD_TYPE")
+        fake_field_type.TINY = 1  # type: ignore
+        fake_field_type.VARCHAR = 15  # type: ignore
+
+        fake_constants = ModuleType("pymysql.constants")
+        fake_constants.FIELD_TYPE = fake_field_type  # type: ignore
+
+        fake_pymysql = ModuleType("pymysql")
+        fake_pymysql.constants = fake_constants  # type: ignore
+
+        original_import = builtins.__import__
+
+        def mock_import(name: str, *args: Any, **kwargs: Any) -> Any:
+            if name == "MySQLdb":
+                raise ImportError("No module named 'MySQLdb'")
+            if name == "pymysql":
+                return fake_pymysql
+            return original_import(name, *args, **kwargs)
+
+        with patch("builtins.__import__", side_effect=mock_import):
+            assert MySQLEngineSpec.get_datatype(1) == "TINY"
+            assert MySQLEngineSpec.get_datatype(15) == "VARCHAR"
+            assert MySQLEngineSpec.get_datatype("BIGINT") == "BIGINT"
+            assert MySQLEngineSpec.get_datatype(999) is None
+    finally:
+        # Restore original state
+        MySQLEngineSpec.type_code_map = original_type_code_map