# #region Test.DbExecutor [C:3] [TYPE Module] [SEMANTICS test,db,executor]
# @BRIEF Verify DbExecutor contracts — execute_sql routing, dialect executors, fetch_schema, pool caching.
# @RELATION BINDS_TO -> [Core.DbExecutor]
# @TEST_EDGE: connection_not_found -> execute_sql returns error result
# @TEST_EDGE: unsupported_dialect -> execute_sql returns error result
# @TEST_EDGE: driver_import_failure -> each dialect returns error when driver missing
# @TEST_EDGE: exception_during_execution -> execute_sql catches and returns error with timing
# @TEST_EDGE: pool_reuse -> second call reuses cached pool/client
# @TEST_EDGE: pool_invalidation -> config change (updated_at) triggers pool recreation
# @TEST_EDGE: pg_status_parsing -> various asyncpg status strings parsed correctly
# @TEST_EDGE: ch_non_int_result -> clickhouse non-int result yields rows_affected=0
# @TEST_EDGE: fetch_schema_unknown_dialect -> returns None
# @TEST_EDGE: fetch_schema_connection_not_found -> returns None

from pathlib import Path
import sys

sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))

import pytest
from unittest.mock import MagicMock, AsyncMock, patch
from types import ModuleType

from src.core.db_executor import DbExecutor, DbExecutionResult, DbSchemaColumn


# ---------------------------------------------------------------------------
# Helpers (C1 — anchor pair only)
# ---------------------------------------------------------------------------

# #region _make_conn
def _make_conn(dialect="postgresql", conn_id="conn-1", updated_at="2025-01-01T00:00:00"):
    c = MagicMock()
    c.id = conn_id
    c.dialect = dialect
    c.host = "localhost"
    c.port = 5432
    c.database = "testdb"
    c.username = "user"
    c.password = "pass"
    c.pool_size = 5
    c.updated_at = updated_at
    return c
# #endregion _make_conn

# #region _make_service
def _make_service(conn=None):
    svc = MagicMock()
    svc.get_connection.return_value = conn
    return svc
# #endregion _make_service


# ===========================================================================
# execute_sql — routing & error paths
# ===========================================================================

class TestExecuteSqlRouting:
    """Verify execute_sql dispatches to correct dialect handler and handles errors."""

    # #region test_connection_not_found [C:2] [TYPE Function]
    # @BRIEF execute_sql returns failure when connection_id is unknown.
    async def test_connection_not_found(self):
        svc = _make_service(conn=None)
        executor = DbExecutor(svc)
        result = await executor.execute_sql("nonexistent", "SELECT 1")
        assert result.success is False
        assert "not found" in result.error
    # #endregion test_connection_not_found

    # #region test_unsupported_dialect [C:2] [TYPE Function]
    # @BRIEF execute_sql returns failure for an unsupported dialect.
    async def test_unsupported_dialect(self):
        conn = _make_conn(dialect="oracle")
        svc = _make_service(conn)
        executor = DbExecutor(svc)
        result = await executor.execute_sql(conn.id, "SELECT 1")
        assert result.success is False
        assert "Unsupported dialect" in result.error
    # #endregion test_unsupported_dialect

    # #region test_exception_during_execution [C:2] [TYPE Function]
    # @BRIEF execute_sql catches driver exceptions and returns error with timing.
    async def test_exception_during_execution(self):
        conn = _make_conn(dialect="clickhouse")
        svc = _make_service(conn)
        executor = DbExecutor(svc)
        # Simpler: patch the entire _execute_ch to raise
        with patch.object(executor, "_execute_ch", side_effect=RuntimeError("boom")):
            result = await executor.execute_sql(conn.id, "INSERT INTO t VALUES (1)")
        assert result.success is False
        assert "boom" in result.error
        assert result.execution_time_ms >= 0
    # #endregion test_exception_during_execution


# ===========================================================================
# _execute_pg — PostgreSQL via asyncpg
# ===========================================================================

class TestExecutePg:
    """Verify PostgreSQL execution: pool creation, status parsing, pool reuse."""

    # #region test_pg_success_insert [C:2] [TYPE Function]
    # @BRIEF asyncpg returns 'INSERT 0 5' → rows_affected=5, success=True.
    async def test_pg_success_insert(self):
        conn = _make_conn(dialect="postgresql")
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        mock_pg_conn = AsyncMock()
        mock_pg_conn.execute = AsyncMock(return_value="INSERT 0 5")

        mock_pool_ctx = AsyncMock()
        mock_pool_ctx.__aenter__ = AsyncMock(return_value=mock_pg_conn)
        mock_pool_ctx.__aexit__ = AsyncMock(return_value=False)

        mock_pool = MagicMock()
        mock_pool.acquire.return_value = mock_pool_ctx

        mock_asyncpg = MagicMock()
        mock_asyncpg.create_pool = AsyncMock(return_value=mock_pool)

        with patch.dict(sys.modules, {"asyncpg": mock_asyncpg}):
            result = await executor._execute_pg(conn, "INSERT INTO t VALUES (1)", 0.0)

        assert result.success is True
        assert result.rows_affected == 5
        assert result.execution_time_ms >= 0
    # #endregion test_pg_success_insert

    # #region test_pg_pool_reuse [C:2] [TYPE Function]
    # @BRIEF Second call with same config reuses cached pool (create_pool called once).
    async def test_pg_pool_reuse(self):
        conn = _make_conn(dialect="postgresql")
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        mock_pg_conn = AsyncMock()
        mock_pg_conn.execute = AsyncMock(return_value="INSERT 0 1")

        mock_pool_ctx = AsyncMock()
        mock_pool_ctx.__aenter__ = AsyncMock(return_value=mock_pg_conn)
        mock_pool_ctx.__aexit__ = AsyncMock(return_value=False)

        mock_pool = MagicMock()
        mock_pool.acquire.return_value = mock_pool_ctx

        mock_asyncpg = MagicMock()
        mock_asyncpg.create_pool = AsyncMock(return_value=mock_pool)

        with patch.dict(sys.modules, {"asyncpg": mock_asyncpg}):
            await executor._execute_pg(conn, "INSERT 1", 0.0)
            await executor._execute_pg(conn, "INSERT 2", 0.0)

        assert mock_asyncpg.create_pool.call_count == 1
    # #endregion test_pg_pool_reuse

    # #region test_pg_pool_invalidation [C:2] [TYPE Function]
    # @BRIEF Config change (updated_at) triggers pool recreation.
    async def test_pg_pool_invalidation(self):
        conn = _make_conn(dialect="postgresql", updated_at="v1")
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        mock_pg_conn = AsyncMock()
        mock_pg_conn.execute = AsyncMock(return_value="INSERT 0 1")
        mock_pool_ctx = AsyncMock()
        mock_pool_ctx.__aenter__ = AsyncMock(return_value=mock_pg_conn)
        mock_pool_ctx.__aexit__ = AsyncMock(return_value=False)
        mock_pool = MagicMock()
        mock_pool.acquire.return_value = mock_pool_ctx

        mock_asyncpg = MagicMock()
        mock_asyncpg.create_pool = AsyncMock(return_value=mock_pool)

        with patch.dict(sys.modules, {"asyncpg": mock_asyncpg}):
            await executor._execute_pg(conn, "INSERT 1", 0.0)
            # Simulate config update
            conn.updated_at = "v2"
            await executor._execute_pg(conn, "INSERT 2", 0.0)

        assert mock_asyncpg.create_pool.call_count == 2
    # #endregion test_pg_pool_invalidation

    # #region test_pg_status_empty [C:2] [TYPE Function]
    # @BRIEF asyncpg returns empty status string → rows_affected=0.
    async def test_pg_status_empty(self):
        conn = _make_conn(dialect="postgresql")
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        mock_pg_conn = AsyncMock()
        mock_pg_conn.execute = AsyncMock(return_value="")

        mock_pool_ctx = AsyncMock()
        mock_pool_ctx.__aenter__ = AsyncMock(return_value=mock_pg_conn)
        mock_pool_ctx.__aexit__ = AsyncMock(return_value=False)
        mock_pool = MagicMock()
        mock_pool.acquire.return_value = mock_pool_ctx

        mock_asyncpg = MagicMock()
        mock_asyncpg.create_pool = AsyncMock(return_value=mock_pool)

        with patch.dict(sys.modules, {"asyncpg": mock_asyncpg}):
            result = await executor._execute_pg(conn, "CREATE TABLE t()", 0.0)

        assert result.success is True
        assert result.rows_affected == 0
    # #endregion test_pg_status_empty

    # #region test_pg_status_non_numeric [C:2] [TYPE Function]
    # @BRIEF asyncpg returns non-numeric status → rows_affected=0 (ValueError caught).
    async def test_pg_status_non_numeric(self):
        conn = _make_conn(dialect="postgresql")
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        mock_pg_conn = AsyncMock()
        mock_pg_conn.execute = AsyncMock(return_value="STATEMENT")

        mock_pool_ctx = AsyncMock()
        mock_pool_ctx.__aenter__ = AsyncMock(return_value=mock_pg_conn)
        mock_pool_ctx.__aexit__ = AsyncMock(return_value=False)
        mock_pool = MagicMock()
        mock_pool.acquire.return_value = mock_pool_ctx

        mock_asyncpg = MagicMock()
        mock_asyncpg.create_pool = AsyncMock(return_value=mock_pool)

        with patch.dict(sys.modules, {"asyncpg": mock_asyncpg}):
            result = await executor._execute_pg(conn, "BEGIN", 0.0)

        assert result.success is True
        assert result.rows_affected == 0
    # #endregion test_pg_status_non_numeric

    # #region test_pg_status_single_part [C:2] [TYPE Function]
    # @BRIEF asyncpg returns single-word status → rows_affected=0 (len(parts) < 2).
    async def test_pg_status_single_part(self):
        conn = _make_conn(dialect="postgresql")
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        mock_pg_conn = AsyncMock()
        mock_pg_conn.execute = AsyncMock(return_value="COMMIT")

        mock_pool_ctx = AsyncMock()
        mock_pool_ctx.__aenter__ = AsyncMock(return_value=mock_pg_conn)
        mock_pool_ctx.__aexit__ = AsyncMock(return_value=False)
        mock_pool = MagicMock()
        mock_pool.acquire.return_value = mock_pool_ctx

        mock_asyncpg = MagicMock()
        mock_asyncpg.create_pool = AsyncMock(return_value=mock_pool)

        with patch.dict(sys.modules, {"asyncpg": mock_asyncpg}):
            result = await executor._execute_pg(conn, "COMMIT", 0.0)

        assert result.success is True
        assert result.rows_affected == 0
    # #endregion test_pg_status_single_part

    # #region test_pg_status_non_numeric_last_part [C:2] [TYPE Function]
    # @BRIEF asyncpg returns multi-part status with non-numeric tail → ValueError caught, rows=0.
    async def test_pg_status_non_numeric_last_part(self):
        conn = _make_conn(dialect="postgresql")
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        mock_pg_conn = AsyncMock()
        mock_pg_conn.execute = AsyncMock(return_value="INSERT 0 abc")

        mock_pool_ctx = AsyncMock()
        mock_pool_ctx.__aenter__ = AsyncMock(return_value=mock_pg_conn)
        mock_pool_ctx.__aexit__ = AsyncMock(return_value=False)
        mock_pool = MagicMock()
        mock_pool.acquire.return_value = mock_pool_ctx

        mock_asyncpg = MagicMock()
        mock_asyncpg.create_pool = AsyncMock(return_value=mock_pool)

        with patch.dict(sys.modules, {"asyncpg": mock_asyncpg}):
            result = await executor._execute_pg(conn, "INSERT INTO t VALUES (1)", 0.0)

        assert result.success is True
        assert result.rows_affected == 0
    # #endregion test_pg_status_non_numeric_last_part

    # #region test_pg_import_error [C:2] [TYPE Function]
    # @BRIEF Missing asyncpg package → returns error result.
    async def test_pg_import_error(self):
        conn = _make_conn(dialect="postgresql")
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        # Simulate ImportError by making asyncpg unimportable
        import builtins
        real_import = builtins.__import__

        def fake_import(name, *args, **kwargs):
            if name == "asyncpg":
                raise ImportError("No module named 'asyncpg'")
            return real_import(name, *args, **kwargs)

        with patch.object(builtins, "__import__", side_effect=fake_import):
            result = await executor._execute_pg(conn, "SELECT 1", 0.0)

        assert result.success is False
        assert "asyncpg" in result.error
    # #endregion test_pg_import_error


# ===========================================================================
# _execute_ch — ClickHouse via clickhouse-connect
# ===========================================================================

class TestExecuteCh:
    """Verify ClickHouse execution: client creation, command result, pool reuse."""

    # #region test_ch_success_int_result [C:2] [TYPE Function]
    # @BRIEF clickhouse command returns int → rows_affected=int, success=True.
    def test_ch_success_int_result(self):
        conn = _make_conn(dialect="clickhouse", conn_id="ch-1")
        # clickhouse default port
        conn.port = 8123
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        mock_client = MagicMock()
        mock_client.command.return_value = 42

        mock_ch_mod = MagicMock()
        mock_ch_mod.get_client.return_value = mock_client

        with patch.dict(sys.modules, {"clickhouse_connect": mock_ch_mod}):
            result = executor._execute_ch(conn, "INSERT INTO t VALUES (1)", 0.0)

        assert result.success is True
        assert result.rows_affected == 42
        assert result.execution_time_ms >= 0
    # #endregion test_ch_success_int_result

    # #region test_ch_non_int_result [C:2] [TYPE Function]
    # @BRIEF clickhouse command returns non-int → rows_affected=0.
    def test_ch_non_int_result(self):
        conn = _make_conn(dialect="clickhouse", conn_id="ch-2")
        conn.port = 8123
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        mock_client = MagicMock()
        mock_client.command.return_value = "OK"

        mock_ch_mod = MagicMock()
        mock_ch_mod.get_client.return_value = mock_client

        with patch.dict(sys.modules, {"clickhouse_connect": mock_ch_mod}):
            result = executor._execute_ch(conn, "CREATE TABLE t()", 0.0)

        assert result.success is True
        assert result.rows_affected == 0
    # #endregion test_ch_non_int_result

    # #region test_ch_pool_reuse [C:2] [TYPE Function]
    # @BRIEF Second call reuses cached client (get_client called once).
    def test_ch_pool_reuse(self):
        conn = _make_conn(dialect="clickhouse", conn_id="ch-3")
        conn.port = 8123
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        mock_client = MagicMock()
        mock_client.command.return_value = 1

        mock_ch_mod = MagicMock()
        mock_ch_mod.get_client.return_value = mock_client

        with patch.dict(sys.modules, {"clickhouse_connect": mock_ch_mod}):
            executor._execute_ch(conn, "INSERT 1", 0.0)
            executor._execute_ch(conn, "INSERT 2", 0.0)

        assert mock_ch_mod.get_client.call_count == 1
    # #endregion test_ch_pool_reuse

    # #region test_ch_import_error [C:2] [TYPE Function]
    # @BRIEF Missing clickhouse-connect → returns error result.
    def test_ch_import_error(self):
        conn = _make_conn(dialect="clickhouse", conn_id="ch-4")
        conn.port = 8123
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        import builtins
        real_import = builtins.__import__

        def fake_import(name, *args, **kwargs):
            if name == "clickhouse_connect":
                raise ImportError("No module named 'clickhouse_connect'")
            return real_import(name, *args, **kwargs)

        with patch.object(builtins, "__import__", side_effect=fake_import):
            result = executor._execute_ch(conn, "SELECT 1", 0.0)

        assert result.success is False
        assert "clickhouse-connect" in result.error
    # #endregion test_ch_import_error


# ===========================================================================
# _execute_mysql — MySQL via pymysql
# ===========================================================================

class TestExecuteMysql:
    """Verify MySQL execution: connection creation, cursor execution, pool reuse."""

    # #region test_mysql_success [C:2] [TYPE Function]
    # @BRIEF pymysql cursor.execute returns row count → success=True.
    def test_mysql_success(self):
        conn = _make_conn(dialect="mysql", conn_id="my-1")
        conn.port = 3306
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        mock_cursor = MagicMock()
        mock_cursor.execute.return_value = 3
        mock_cursor.__enter__ = MagicMock(return_value=mock_cursor)
        mock_cursor.__exit__ = MagicMock(return_value=False)

        mock_db = MagicMock()
        mock_db.cursor.return_value = mock_cursor

        mock_pymysql = MagicMock()
        mock_pymysql.connect.return_value = mock_db

        with patch.dict(sys.modules, {"pymysql": mock_pymysql}):
            result = executor._execute_mysql(conn, "INSERT INTO t VALUES (1)", 0.0)

        assert result.success is True
        assert result.rows_affected == 3
        assert result.execution_time_ms >= 0
    # #endregion test_mysql_success

    # #region test_mysql_pool_reuse [C:2] [TYPE Function]
    # @BRIEF Second call reuses cached connection (connect called once).
    def test_mysql_pool_reuse(self):
        conn = _make_conn(dialect="mysql", conn_id="my-2")
        conn.port = 3306
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        mock_cursor = MagicMock()
        mock_cursor.execute.return_value = 1
        mock_cursor.__enter__ = MagicMock(return_value=mock_cursor)
        mock_cursor.__exit__ = MagicMock(return_value=False)

        mock_db = MagicMock()
        mock_db.cursor.return_value = mock_cursor

        mock_pymysql = MagicMock()
        mock_pymysql.connect.return_value = mock_db

        with patch.dict(sys.modules, {"pymysql": mock_pymysql}):
            executor._execute_mysql(conn, "INSERT 1", 0.0)
            executor._execute_mysql(conn, "INSERT 2", 0.0)

        assert mock_pymysql.connect.call_count == 1
    # #endregion test_mysql_pool_reuse

    # #region test_mysql_import_error [C:2] [TYPE Function]
    # @BRIEF Missing pymysql → returns error result.
    def test_mysql_import_error(self):
        conn = _make_conn(dialect="mysql", conn_id="my-3")
        conn.port = 3306
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        import builtins
        real_import = builtins.__import__

        def fake_import(name, *args, **kwargs):
            if name == "pymysql":
                raise ImportError("No module named 'pymysql'")
            return real_import(name, *args, **kwargs)

        with patch.object(builtins, "__import__", side_effect=fake_import):
            result = executor._execute_mysql(conn, "SELECT 1", 0.0)

        assert result.success is False
        assert "pymysql" in result.error
    # #endregion test_mysql_import_error


# ===========================================================================
# fetch_schema — dialect routing & schema fetching
# ===========================================================================

class TestFetchSchema:
    """Verify fetch_schema dispatches to correct dialect fetcher and handles errors."""

    # #region test_fetch_schema_connection_not_found [C:2] [TYPE Function]
    # @BRIEF fetch_schema returns None when connection_id is unknown.
    async def test_fetch_schema_connection_not_found(self):
        executor = DbExecutor(MagicMock())
        mock_svc = MagicMock()
        mock_svc.get_connection.return_value = None

        with patch("src.core.connection_service.ConnectionService", return_value=mock_svc):
            result = await executor.fetch_schema("nonexistent", "public", "users", MagicMock())

        assert result is None
    # #endregion test_fetch_schema_connection_not_found

    # #region test_fetch_schema_clickhouse [C:2] [TYPE Function]
    # @BRIEF fetch_schema routes to _fetch_ch for clickhouse dialect.
    async def test_fetch_schema_clickhouse(self):
        conn = _make_conn(dialect="clickhouse")
        executor = DbExecutor(MagicMock())
        mock_svc = MagicMock()
        mock_svc.get_connection.return_value = conn

        expected = [DbSchemaColumn(name="id", data_type="UInt64")]
        with patch("src.core.connection_service.ConnectionService", return_value=mock_svc), \
             patch.object(executor, "_fetch_ch", return_value=expected) as mock_fetch:
            result = await executor.fetch_schema("ch-1", "default", "events", MagicMock())

        assert result == expected
        mock_fetch.assert_called_once()
        # Verify SQL contains system.columns
        call_sql = mock_fetch.call_args[0][1]
        assert "system.columns" in call_sql
    # #endregion test_fetch_schema_clickhouse

    # #region test_fetch_schema_postgresql [C:2] [TYPE Function]
    # @BRIEF fetch_schema routes to _fetch_pg for postgresql dialect.
    async def test_fetch_schema_postgresql(self):
        conn = _make_conn(dialect="postgresql")
        executor = DbExecutor(MagicMock())
        mock_svc = MagicMock()
        mock_svc.get_connection.return_value = conn

        expected = [DbSchemaColumn(name="id", data_type="integer")]
        mock_fetch = AsyncMock(return_value=expected)
        with patch("src.core.connection_service.ConnectionService", return_value=mock_svc), \
             patch.object(executor, "_fetch_pg", mock_fetch):
            result = await executor.fetch_schema("pg-1", "public", "users", MagicMock())

        assert result == expected
        mock_fetch.assert_called_once()
        call_sql = mock_fetch.call_args[0][1]
        assert "information_schema.columns" in call_sql
    # #endregion test_fetch_schema_postgresql

    # #region test_fetch_schema_mysql [C:2] [TYPE Function]
    # @BRIEF fetch_schema routes to _fetch_mysql for mysql dialect.
    async def test_fetch_schema_mysql(self):
        conn = _make_conn(dialect="mysql")
        executor = DbExecutor(MagicMock())
        mock_svc = MagicMock()
        mock_svc.get_connection.return_value = conn

        expected = [DbSchemaColumn(name="id", data_type="int")]
        with patch("src.core.connection_service.ConnectionService", return_value=mock_svc), \
             patch.object(executor, "_fetch_mysql", return_value=expected) as mock_fetch:
            result = await executor.fetch_schema("my-1", "mydb", "users", MagicMock())

        assert result == expected
        mock_fetch.assert_called_once()
        call_sql = mock_fetch.call_args[0][1]
        assert "information_schema.columns" in call_sql
    # #endregion test_fetch_schema_mysql

    # #region test_fetch_schema_unsupported_dialect [C:2] [TYPE Function]
    # @BRIEF fetch_schema returns None for unsupported dialect.
    async def test_fetch_schema_unsupported_dialect(self):
        conn = _make_conn(dialect="oracle")
        executor = DbExecutor(MagicMock())
        mock_svc = MagicMock()
        mock_svc.get_connection.return_value = conn

        with patch("src.core.connection_service.ConnectionService", return_value=mock_svc):
            result = await executor.fetch_schema("ora-1", "dbo", "users", MagicMock())

        assert result is None
    # #endregion test_fetch_schema_unsupported_dialect

    # #region test_fetch_schema_sql_injection_escaping [C:2] [TYPE Function]
    # @BRIEF fetch_schema escapes single quotes in schema/table names.
    async def test_fetch_schema_sql_injection_escaping(self):
        conn = _make_conn(dialect="postgresql")
        executor = DbExecutor(MagicMock())
        mock_svc = MagicMock()
        mock_svc.get_connection.return_value = conn

        mock_fetch = AsyncMock(return_value=[])
        with patch("src.core.connection_service.ConnectionService", return_value=mock_svc), \
             patch.object(executor, "_fetch_pg", mock_fetch):
            await executor.fetch_schema("pg-1", "pub'lic", "us'ers", MagicMock())

        call_sql = mock_fetch.call_args[0][1]
        assert "pub''lic" in call_sql
        assert "us''ers" in call_sql
    # #endregion test_fetch_schema_sql_injection_escaping


# ===========================================================================
# _fetch_pg — PostgreSQL schema fetch
# ===========================================================================

class TestFetchPg:
    """Verify _fetch_pg: pool creation, row mapping, import error."""

    # #region test_fetch_pg_success [C:2] [TYPE Function]
    # @BRIEF asyncpg fetch returns rows → mapped to DbSchemaColumn list.
    async def test_fetch_pg_success(self):
        conn = _make_conn(dialect="postgresql", conn_id="pg-f1")
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        fake_rows = [
            {"name": "id", "type": "integer"},
            {"name": "email", "type": "varchar"},
        ]

        mock_pg_conn = AsyncMock()
        mock_pg_conn.fetch = AsyncMock(return_value=fake_rows)

        mock_pool_ctx = AsyncMock()
        mock_pool_ctx.__aenter__ = AsyncMock(return_value=mock_pg_conn)
        mock_pool_ctx.__aexit__ = AsyncMock(return_value=False)

        mock_pool = MagicMock()
        mock_pool.acquire.return_value = mock_pool_ctx

        mock_asyncpg = MagicMock()
        mock_asyncpg.create_pool = AsyncMock(return_value=mock_pool)

        with patch.dict(sys.modules, {"asyncpg": mock_asyncpg}):
            result = await executor._fetch_pg(conn, "SELECT column_name, data_type FROM information_schema.columns")

        assert result is not None
        assert len(result) == 2
        assert result[0].name == "id"
        assert result[0].data_type == "integer"
        assert result[1].name == "email"
        assert result[1].data_type == "varchar"
    # #endregion test_fetch_pg_success

    # #region test_fetch_pg_import_error [C:2] [TYPE Function]
    # @BRIEF Missing asyncpg → _fetch_pg returns None.
    async def test_fetch_pg_import_error(self):
        conn = _make_conn(dialect="postgresql", conn_id="pg-f2")
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        import builtins
        real_import = builtins.__import__

        def fake_import(name, *args, **kwargs):
            if name == "asyncpg":
                raise ImportError("No module named 'asyncpg'")
            return real_import(name, *args, **kwargs)

        with patch.object(builtins, "__import__", side_effect=fake_import):
            result = await executor._fetch_pg(conn, "SELECT 1")

        assert result is None
    # #endregion test_fetch_pg_import_error


# ===========================================================================
# _fetch_ch — ClickHouse schema fetch
# ===========================================================================

class TestFetchCh:
    """Verify _fetch_ch: client creation, row mapping, empty result, import error."""

    # #region test_fetch_ch_success [C:2] [TYPE Function]
    # @BRIEF clickhouse query returns rows → mapped to DbSchemaColumn list.
    def test_fetch_ch_success(self):
        conn = _make_conn(dialect="clickhouse", conn_id="ch-f1")
        conn.port = 8123
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        mock_query_result = MagicMock()
        mock_query_result.result_rows = [
            ("id", "UInt64"),
            ("name", "String"),
        ]

        mock_client = MagicMock()
        mock_client.query.return_value = mock_query_result

        mock_ch_mod = MagicMock()
        mock_ch_mod.get_client.return_value = mock_client

        with patch.dict(sys.modules, {"clickhouse_connect": mock_ch_mod}):
            result = executor._fetch_ch(conn, "SELECT name, type FROM system.columns")

        assert result is not None
        assert len(result) == 2
        assert result[0].name == "id"
        assert result[0].data_type == "UInt64"
        assert result[1].name == "name"
        assert result[1].data_type == "String"
    # #endregion test_fetch_ch_success

    # #region test_fetch_ch_empty_rows [C:2] [TYPE Function]
    # @BRIEF clickhouse query returns empty rows → returns empty list.
    def test_fetch_ch_empty_rows(self):
        conn = _make_conn(dialect="clickhouse", conn_id="ch-f2")
        conn.port = 8123
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        mock_query_result = MagicMock()
        mock_query_result.result_rows = []

        mock_client = MagicMock()
        mock_client.query.return_value = mock_query_result

        mock_ch_mod = MagicMock()
        mock_ch_mod.get_client.return_value = mock_client

        with patch.dict(sys.modules, {"clickhouse_connect": mock_ch_mod}):
            result = executor._fetch_ch(conn, "SELECT name, type FROM system.columns")

        assert result == []
    # #endregion test_fetch_ch_empty_rows

    # #region test_fetch_ch_import_error [C:2] [TYPE Function]
    # @BRIEF Missing clickhouse-connect → _fetch_ch returns None.
    def test_fetch_ch_import_error(self):
        conn = _make_conn(dialect="clickhouse", conn_id="ch-f3")
        conn.port = 8123
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        import builtins
        real_import = builtins.__import__

        def fake_import(name, *args, **kwargs):
            if name == "clickhouse_connect":
                raise ImportError("No module named 'clickhouse_connect'")
            return real_import(name, *args, **kwargs)

        with patch.object(builtins, "__import__", side_effect=fake_import):
            result = executor._fetch_ch(conn, "SELECT 1")

        assert result is None
    # #endregion test_fetch_ch_import_error


# ===========================================================================
# _fetch_mysql — MySQL schema fetch
# ===========================================================================

class TestFetchMysql:
    """Verify _fetch_mysql: connection creation, fetchall mapping, empty result, import error."""

    # #region test_fetch_mysql_success [C:2] [TYPE Function]
    # @BRIEF pymysql fetchall returns rows → mapped to DbSchemaColumn list.
    def test_fetch_mysql_success(self):
        conn = _make_conn(dialect="mysql", conn_id="my-f1")
        conn.port = 3306
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        fake_rows = [
            ("id", "int"),
            ("name", "varchar(255)"),
        ]

        mock_cursor = MagicMock()
        mock_cursor.fetchall.return_value = fake_rows
        mock_cursor.__enter__ = MagicMock(return_value=mock_cursor)
        mock_cursor.__exit__ = MagicMock(return_value=False)

        mock_db = MagicMock()
        mock_db.cursor.return_value = mock_cursor

        mock_pymysql = MagicMock()
        mock_pymysql.connect.return_value = mock_db

        with patch.dict(sys.modules, {"pymysql": mock_pymysql}):
            result = executor._fetch_mysql(conn, "SELECT column_name, data_type FROM information_schema.columns")

        assert result is not None
        assert len(result) == 2
        assert result[0].name == "id"
        assert result[0].data_type == "int"
        assert result[1].name == "name"
        assert result[1].data_type == "varchar(255)"
    # #endregion test_fetch_mysql_success

    # #region test_fetch_mysql_empty_rows [C:2] [TYPE Function]
    # @BRIEF pymysql fetchall returns empty → returns empty list.
    def test_fetch_mysql_empty_rows(self):
        conn = _make_conn(dialect="mysql", conn_id="my-f2")
        conn.port = 3306
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        mock_cursor = MagicMock()
        mock_cursor.fetchall.return_value = []
        mock_cursor.__enter__ = MagicMock(return_value=mock_cursor)
        mock_cursor.__exit__ = MagicMock(return_value=False)

        mock_db = MagicMock()
        mock_db.cursor.return_value = mock_cursor

        mock_pymysql = MagicMock()
        mock_pymysql.connect.return_value = mock_db

        with patch.dict(sys.modules, {"pymysql": mock_pymysql}):
            result = executor._fetch_mysql(conn, "SELECT column_name, data_type FROM information_schema.columns")

        assert result == []
    # #endregion test_fetch_mysql_empty_rows

    # #region test_fetch_mysql_import_error [C:2] [TYPE Function]
    # @BRIEF Missing pymysql → _fetch_mysql returns None.
    def test_fetch_mysql_import_error(self):
        conn = _make_conn(dialect="mysql", conn_id="my-f3")
        conn.port = 3306
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        import builtins
        real_import = builtins.__import__

        def fake_import(name, *args, **kwargs):
            if name == "pymysql":
                raise ImportError("No module named 'pymysql'")
            return real_import(name, *args, **kwargs)

        with patch.object(builtins, "__import__", side_effect=fake_import):
            result = executor._fetch_mysql(conn, "SELECT 1")

        assert result is None
    # #endregion test_fetch_mysql_import_error


# ===========================================================================
# execute_sql — full integration through dialect handlers
# ===========================================================================

class TestExecuteSqlDialects:
    """Verify execute_sql dispatches correctly to each dialect handler via mocked drivers."""

    # #region test_execute_sql_postgresql [C:2] [TYPE Function]
    # @BRIEF execute_sql routes postgresql to _execute_pg and returns its result.
    async def test_execute_sql_postgresql(self):
        conn = _make_conn(dialect="postgresql")
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        expected = DbExecutionResult(success=True, rows_affected=10, execution_time_ms=50.0)
        mock_pg = AsyncMock(return_value=expected)
        with patch.object(executor, "_execute_pg", mock_pg):
            result = await executor.execute_sql(conn.id, "INSERT INTO t VALUES (1)")

        assert result.success is True
        assert result.rows_affected == 10
        mock_pg.assert_called_once_with(conn, "INSERT INTO t VALUES (1)", mock_pg.call_args[0][2])
    # #endregion test_execute_sql_postgresql

    # #region test_execute_sql_clickhouse [C:2] [TYPE Function]
    # @BRIEF execute_sql routes clickhouse to _execute_ch and returns its result.
    async def test_execute_sql_clickhouse(self):
        conn = _make_conn(dialect="clickhouse")
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        expected = DbExecutionResult(success=True, rows_affected=7, execution_time_ms=30.0)
        with patch.object(executor, "_execute_ch", return_value=expected) as mock_ch:
            result = await executor.execute_sql(conn.id, "INSERT INTO t VALUES (1)")

        assert result.success is True
        assert result.rows_affected == 7
        mock_ch.assert_called_once()
    # #endregion test_execute_sql_clickhouse

    # #region test_execute_sql_mysql [C:2] [TYPE Function]
    # @BRIEF execute_sql routes mysql to _execute_mysql and returns its result.
    async def test_execute_sql_mysql(self):
        conn = _make_conn(dialect="mysql")
        svc = _make_service(conn)
        executor = DbExecutor(svc)

        expected = DbExecutionResult(success=True, rows_affected=3, execution_time_ms=20.0)
        with patch.object(executor, "_execute_mysql", return_value=expected) as mock_my:
            result = await executor.execute_sql(conn.id, "INSERT INTO t VALUES (1)")

        assert result.success is True
        assert result.rows_affected == 3
        mock_my.assert_called_once()
    # #endregion test_execute_sql_mysql


# ===========================================================================
# Dataclass sanity
# ===========================================================================

class TestDataclasses:
    """Verify DbExecutionResult and DbSchemaColumn default values."""

    # #region test_db_execution_result_defaults [C:2] [TYPE Function]
    # @BRIEF DbExecutionResult defaults: rows_affected=0, execution_time_ms=0.0, error=None, db_version=None.
    def test_db_execution_result_defaults(self):
        r = DbExecutionResult(success=True)
        assert r.rows_affected == 0
        assert r.execution_time_ms == 0.0
        assert r.error is None
        assert r.db_version is None
    # #endregion test_db_execution_result_defaults

    # #region test_db_schema_column_defaults [C:2] [TYPE Function]
    # @BRIEF DbSchemaColumn defaults: data_type=None.
    def test_db_schema_column_defaults(self):
        c = DbSchemaColumn(name="id")
        assert c.name == "id"
        assert c.data_type is None
    # #endregion test_db_schema_column_defaults

# #endregion Test.DbExecutor
