aminghadersohi commented on code in PR #45071:
URL: https://github.com/apache/superset/pull/45071#discussion_r4224443075


##########
superset/commands/semantic_layer/delete.py:
##########
@@ -32,11 +39,214 @@
 )
 from superset.commands.utils import current_user_can_modify_object
 from superset.daos.semantic_layer import SemanticLayerDAO, SemanticViewDAO
+from superset.dashboards.filters import DashboardAccessFilter
 from superset.exceptions import SupersetSecurityException
+from superset.models.dashboard import Dashboard, dashboard_slices
+from superset.models.slice import Slice
+from superset.reports.filters import ReportScheduleFilter
+from superset.reports.models import ReportSchedule
 from superset.semantic_layers.models import SemanticLayer, SemanticView
+from superset.utils import json
 from superset.utils.decorators import on_error, transaction
 
 logger = logging.getLogger(__name__)
+_DEPENDENT_LIMIT: int = 20
+
+
+def _semantic_target_matches(target: object, view_ids: set[int]) -> bool:
+    """A missing type denotes a table, even when its integer ID collides."""
+    if not isinstance(target, dict) or target.get("datasourceType") != 
"semantic_view":
+        return False
+    raw_id: object = target.get("datasetId")
+    if isinstance(raw_id, bool) or not (
+        isinstance(raw_id, int) or (isinstance(raw_id, str) and 
raw_id.isdecimal())
+    ):
+        return False
+    try:
+        return int(raw_id) in view_ids
+    except ValueError:
+        return False
+
+
+def _dashboard_targets_view(
+    metadata: str, view_ids: set[int], dashboard_id: int
+) -> bool:
+    """Match typed semantic targets in native filters or display controls."""
+    try:
+        decoded: object = json.loads(metadata)
+    except (TypeError, ValueError, RecursionError):
+        logger.debug("Skipping malformed targets on dashboard %s", 
dashboard_id)
+        return False
+    if not isinstance(decoded, dict):
+        logger.debug("Skipping non-object targets on dashboard %s", 
dashboard_id)
+        return False
+    for key in ("native_filter_configuration", "chart_customization_config"):
+        controls: object = decoded.get(key)
+        if not isinstance(controls, list):
+            continue
+        for control in controls:
+            if not isinstance(control, dict):
+                continue
+            targets: object = control.get("targets")
+            if isinstance(targets, list) and any(
+                _semantic_target_matches(target, view_ids) for target in 
targets
+            ):
+                return True
+    return False
+
+
+def _dashboard_target_ids(view_ids: sa.Select) -> set[int]:
+    """Find live dashboards with semantic targets after a coarse SQL 
prefilter."""
+    selected_ids: set[int] = set(db.session.execute(view_ids).scalars())
+    if not selected_ids:
+        return set()
+    candidates: sa.Select = sa.select(Dashboard.id, 
Dashboard.json_metadata).where(
+        Dashboard.deleted_at.is_(None),
+        Dashboard.json_metadata.like("%semantic_view%"),
+    )

Review Comment:
   Both points are disclosed in the PR body. The scan is inherent: 
`json_metadata` is unindexed MediumText and native-filter targets exist only 
there, so no pattern avoids it. It runs only on a semantic-source delete, so 
`Occurrence: Often` overstates it.



##########
superset/commands/semantic_layer/delete.py:
##########
@@ -32,11 +39,214 @@
 )
 from superset.commands.utils import current_user_can_modify_object
 from superset.daos.semantic_layer import SemanticLayerDAO, SemanticViewDAO
+from superset.dashboards.filters import DashboardAccessFilter
 from superset.exceptions import SupersetSecurityException
+from superset.models.dashboard import Dashboard, dashboard_slices
+from superset.models.slice import Slice
+from superset.reports.filters import ReportScheduleFilter
+from superset.reports.models import ReportSchedule
 from superset.semantic_layers.models import SemanticLayer, SemanticView
+from superset.utils import json
 from superset.utils.decorators import on_error, transaction
 
 logger = logging.getLogger(__name__)
+_DEPENDENT_LIMIT: int = 20
+
+
+def _semantic_target_matches(target: object, view_ids: set[int]) -> bool:
+    """A missing type denotes a table, even when its integer ID collides."""
+    if not isinstance(target, dict) or target.get("datasourceType") != 
"semantic_view":
+        return False
+    raw_id: object = target.get("datasetId")
+    if isinstance(raw_id, bool) or not (
+        isinstance(raw_id, int) or (isinstance(raw_id, str) and 
raw_id.isdecimal())
+    ):
+        return False
+    try:
+        return int(raw_id) in view_ids
+    except ValueError:
+        return False
+
+
+def _dashboard_targets_view(
+    metadata: str, view_ids: set[int], dashboard_id: int
+) -> bool:
+    """Match typed semantic targets in native filters or display controls."""
+    try:
+        decoded: object = json.loads(metadata)
+    except (TypeError, ValueError, RecursionError):
+        logger.debug("Skipping malformed targets on dashboard %s", 
dashboard_id)
+        return False
+    if not isinstance(decoded, dict):
+        logger.debug("Skipping non-object targets on dashboard %s", 
dashboard_id)
+        return False
+    for key in ("native_filter_configuration", "chart_customization_config"):
+        controls: object = decoded.get(key)
+        if not isinstance(controls, list):
+            continue
+        for control in controls:
+            if not isinstance(control, dict):
+                continue
+            targets: object = control.get("targets")
+            if isinstance(targets, list) and any(
+                _semantic_target_matches(target, view_ids) for target in 
targets
+            ):
+                return True
+    return False
+
+
+def _dashboard_target_ids(view_ids: sa.Select) -> set[int]:
+    """Find live dashboards with semantic targets after a coarse SQL 
prefilter."""
+    selected_ids: set[int] = set(db.session.execute(view_ids).scalars())
+    if not selected_ids:
+        return set()
+    candidates: sa.Select = sa.select(Dashboard.id, 
Dashboard.json_metadata).where(
+        Dashboard.deleted_at.is_(None),
+        Dashboard.json_metadata.like("%semantic_view%"),
+    )
+    matched_ids: set[int] = set()
+    for dashboard_id, metadata in db.session.execute(
+        candidates.execution_options(yield_per=1000)
+    ):
+        if _dashboard_targets_view(metadata, selected_ids, dashboard_id):
+            matched_ids.add(dashboard_id)
+    return matched_ids
+
+
+def _dependent_assets(
+    view_ids: sa.Select,
+) -> tuple[int, list[dict[str, str | int]], int]:
+    """Count dependents, naming only assets visible through their list APIs."""
+    chart_ids: sa.Select = sa.select(Slice.id).where(
+        Slice.datasource_type == "semantic_view",
+        Slice.datasource_id.in_(view_ids),
+        Slice.deleted_at.is_(None),
+    )
+    chart_dashboard_ids: sa.Select = (
+        sa.select(dashboard_slices.c.dashboard_id)
+        .join(Slice, dashboard_slices.c.slice_id == Slice.id)
+        .join(Dashboard, dashboard_slices.c.dashboard_id == Dashboard.id)
+        .where(
+            Slice.id.in_(chart_ids),
+            Dashboard.deleted_at.is_(None),
+        )
+        .distinct()
+    )
+    dashboard_target_ids: set[int] = _dashboard_target_ids(view_ids)
+    dashboard_ids: sa.Select = sa.select(Dashboard.id).where(
+        Dashboard.deleted_at.is_(None),
+        sa.or_(
+            Dashboard.id.in_(chart_dashboard_ids),
+            Dashboard.id.in_(dashboard_target_ids),
+        ),
+    )
+    dependents: sa.Subquery = sa.union_all(
+        sa.select(
+            sa.literal("chart").label("type"),
+            Slice.id.label("id"),
+            sa.func.coalesce(Slice.slice_name, 
str(_("Untitled"))).label("name"),
+        ).where(Slice.id.in_(chart_ids)),
+        sa.select(
+            sa.literal("dashboard").label("type"),
+            Dashboard.id.label("id"),
+            sa.func.coalesce(Dashboard.dashboard_title, 
str(_("Untitled"))).label(
+                "name"
+            ),
+        ).where(Dashboard.id.in_(dashboard_ids)),
+        sa.select(
+            sa.func.lower(ReportSchedule.type).label("type"),
+            ReportSchedule.id.label("id"),
+            ReportSchedule.name.label("name"),
+        ).where(
+            ReportSchedule.active.is_(True),
+            sa.or_(
+                ReportSchedule.chart_id.in_(chart_ids),
+                ReportSchedule.dashboard_id.in_(dashboard_ids),
+            ),
+        ),
+    ).subquery()
+    visible_chart_ids: sa.Select = (
+        cast(
+            sa.Select,
+            ChartFilter("id", SQLAInterface(Slice, db.session))
+            .apply(db.session.query(Slice.id), None)
+            .statement,
+        )
+        if security_manager.can_access("can_read", "Chart")
+        else sa.select(Slice.id).where(sa.false())
+    )
+    visible_dashboard_ids: sa.Select = (
+        cast(
+            sa.Select,
+            DashboardAccessFilter("id", SQLAInterface(Dashboard, db.session))
+            .apply(db.session.query(Dashboard.id), None)
+            .statement,
+        )
+        if security_manager.can_access("can_read", "Dashboard")
+        else sa.select(Dashboard.id).where(sa.false())

Review Comment:
   Only the Chart arm of this `can_read` gate is pinned: flipping this `else` 
or line 196's to `sa.true()` keeps every test green and would name dashboards 
or schedules the caller cannot list. A dashboard plus an active report in 
`test_semantic_delete_hides_unreadable_dependent` kills both.



##########
tests/unit_tests/commands/semantic_layer/delete_test.py:
##########
@@ -161,6 +206,523 @@ def test_delete_semantic_view_success(mocker: 
MockerFixture) -> None:
     dao.delete.assert_called_once_with([mock_model])
 
 
+def test_delete_semantic_view_refuses_dependents(mocker: MockerFixture) -> 
None:
+    """A view still used by a chart must not be hard-deleted."""
+    from superset.commands.semantic_layer.delete import 
DeleteSemanticViewCommand
+
+    model: MagicMock = MagicMock()
+    model.id = 42
+    dao: MagicMock = mocker.patch(
+        "superset.commands.semantic_layer.delete.SemanticViewDAO"
+    )
+    dao.find_by_id.return_value = model
+    mocker.patch(
+        
"superset.commands.semantic_layer.delete.current_user_can_modify_object",
+        return_value=True,
+    )
+    mocker.patch(
+        "superset.commands.semantic_layer.delete._dependent_assets",
+        return_value=(1, [{"type": "chart", "id": 7, "name": "Revenue"}], 0),
+    )
+
+    exc_info: pytest.ExceptionInfo[SemanticDeleteDependentsError]
+    with pytest.raises(SemanticDeleteDependentsError) as exc_info:
+        DeleteSemanticViewCommand(42).run()
+
+    dao.delete.assert_not_called()
+    assert exc_info.value.total == 1
+    assert exc_info.value.dependents == [{"type": "chart", "id": 7, "name": 
"Revenue"}]
+
+
+def test_bulk_delete_semantic_views_refuses_all_on_one_dependency(
+    mocker: MockerFixture,
+) -> None:
+    """A blocked view prevents every member of the bulk hard delete."""
+    from superset.commands.semantic_layer.delete import 
BulkDeleteSemanticViewCommand
+
+    models: list[MagicMock] = [MagicMock(id=42), MagicMock(id=43)]
+    dao: MagicMock = mocker.patch(
+        "superset.commands.semantic_layer.delete.SemanticViewDAO"
+    )
+    dao.find_by_ids.return_value = models
+    mocker.patch(
+        
"superset.commands.semantic_layer.delete.current_user_can_modify_object",
+        return_value=True,
+    )
+    mocker.patch(
+        "superset.commands.semantic_layer.delete._dependent_assets",
+        return_value=(1, [{"type": "chart", "id": 7, "name": "Revenue"}], 0),
+    )
+
+    with pytest.raises(SemanticDeleteDependentsError):
+        BulkDeleteSemanticViewCommand([42, 43]).run()
+
+    dao.delete.assert_not_called()
+
+
+def test_semantic_delete_lists_live_dependents_only(
+    session: Session, mocker: MockerFixture
+) -> None:
+    """Charts, dashboard membership and active schedules form the dependency 
set."""
+    from datetime import datetime
+
+    from superset.commands.semantic_layer.delete import _dependent_assets
+    from superset.models.dashboard import Dashboard
+    from superset.models.slice import Slice
+    from superset.reports.models import ReportSchedule
+
+    Slice.metadata.create_all(session.get_bind())  # pylint: disable=no-member
+    connection: Connection = session.get_bind().connect()
+    connection.execute(
+        Slice.__table__.insert().values(  # pylint: disable=no-member
+            id=711,
+            slice_name="Live chart",
+            datasource_type="semantic_view",
+            datasource_id=42,
+        )
+    )
+    connection.execute(
+        Slice.__table__.insert().values(  # pylint: disable=no-member
+            id=712,
+            slice_name="Deleted chart",
+            datasource_type="semantic_view",
+            datasource_id=42,
+            deleted_at=datetime(2026, 1, 1),
+        )
+    )
+    connection.execute(
+        Slice.__table__.insert().values(  # pylint: disable=no-member
+            id=717,
+            slice_name=None,
+            datasource_type="semantic_view",
+            datasource_id=42,
+        )
+    )
+    connection.execute(
+        Dashboard.__table__.insert().values(  # pylint: disable=no-member
+            id=713, dashboard_title="Live dashboard"
+        )
+    )
+    connection.exec_driver_sql(
+        "INSERT INTO dashboard_slices (dashboard_id, slice_id) VALUES (713, 
711)"
+    )
+    connection.execute(
+        ReportSchedule.__table__.insert(),  # pylint: disable=no-member
+        [
+            {
+                "id": 714,
+                "type": "Alert",
+                "name": "Live alert",
+                "crontab": "* * * * *",
+                "chart_id": 711,
+                "dashboard_id": None,
+                "active": True,
+            },
+            {
+                "id": 715,
+                "type": "Report",
+                "name": "Live report",
+                "crontab": "* * * * *",
+                "dashboard_id": 713,
+                "chart_id": None,
+                "active": True,
+            },
+            {
+                "id": 716,
+                "type": "Report",
+                "name": "Inactive report",
+                "crontab": "* * * * *",
+                "chart_id": 711,
+                "dashboard_id": None,
+                "active": False,
+            },
+        ],
+    )
+    mocker.patch("superset.db.session.scalar", side_effect=connection.scalar)
+    mocker.patch("superset.db.session.execute", side_effect=connection.execute)
+    mocker.patch("superset.security_manager.can_access", return_value=True)
+    mocker.patch(
+        "superset.security_manager.can_access_all_datasources", 
return_value=True
+    )
+    mocker.patch("superset.security_manager.is_admin", return_value=True)
+
+    total, dependents, inaccessible_count = 
_dependent_assets(sa.select(sa.literal(42)))
+
+    assert total == 5
+    assert inaccessible_count == 0
+    assert {item["name"] for item in dependents} == {
+        "Live chart",
+        "Live dashboard",
+        "Live alert",
+        "Live report",
+        "Untitled",
+    }
+    assert {item["type"] for item in dependents} == {
+        "chart",
+        "dashboard",
+        "alert",
+        "report",
+    }
+    mocker.patch("superset.commands.semantic_layer.delete._DEPENDENT_LIMIT", 2)
+    capped_total, capped_dependents, capped_inaccessible_count = 
_dependent_assets(
+        sa.select(sa.literal(42))
+    )
+    assert capped_total == 5
+    assert len(capped_dependents) == 2
+    assert capped_inaccessible_count == 0
+    mocker.patch(
+        "superset.commands.semantic_layer.delete.ChartFilter.apply",
+        return_value=session.query(Slice.id).filter(sa.false()),
+    )
+    hidden_total, hidden_dependents, hidden_count = _dependent_assets(
+        sa.select(sa.literal(42))
+    )
+    assert hidden_total == 5
+    assert hidden_count == 2
+    assert all(dependent["type"] != "chart" for dependent in hidden_dependents)
+    connection.close()
+
+
+def test_semantic_delete_finds_native_filter_dashboards(
+    session: Session, mocker: MockerFixture
+) -> None:
+    """Only live, typed semantic targets block deletion; membership is 
deduped."""
+    import uuid
+    from datetime import datetime
+
+    from superset.commands.semantic_layer.delete import (
+        _dependent_assets,
+        DeleteSemanticViewCommand,
+    )
+    from superset.models.dashboard import Dashboard
+    from superset.models.slice import Slice
+    from superset.reports.models import ReportSchedule
+    from superset.semantic_layers.models import SemanticLayer, SemanticView
+    from superset.utils import json
+
+    Slice.metadata.create_all(session.get_bind())  # pylint: disable=no-member
+    connection: Connection = session.get_bind().connect()
+    layer_uuid: uuid.UUID = uuid.uuid4()
+
+    def metadata(source_type: str | None, dataset_id: int | str) -> str:
+        target: dict[str, str | int] = {"datasetId": dataset_id}
+        if source_type is not None:
+            target["datasourceType"] = source_type
+        return json.dumps({"native_filter_configuration": [{"targets": 
[target]}]})
+
+    try:
+        connection.execute(
+            SemanticLayer.__table__.insert().values(  # pylint: 
disable=no-member
+                uuid=layer_uuid, name="Source", type="test"
+            )
+        )
+        connection.execute(
+            SemanticView.__table__.insert().values(  # pylint: 
disable=no-member
+                id=42, name="View", semantic_layer_uuid=layer_uuid
+            )
+        )
+        connection.execute(
+            Dashboard.__table__.insert(),  # pylint: disable=no-member
+            [
+                {
+                    "id": 731,
+                    "dashboard_title": "Typed integer",
+                    "json_metadata": metadata("semantic_view", 42),
+                },
+                {
+                    "id": 732,
+                    "dashboard_title": "Typed string",
+                    "json_metadata": metadata("semantic_view", "42"),
+                },
+                {
+                    "id": 733,
+                    "dashboard_title": "Table",
+                    "json_metadata": json.dumps(
+                        {
+                            "note": "semantic_view",
+                            "native_filter_configuration": [
+                                {
+                                    "targets": [
+                                        {"datasetId": 42, "datasourceType": 
"table"}
+                                    ]
+                                }
+                            ],
+                        }
+                    ),
+                },
+                {
+                    "id": 734,
+                    "dashboard_title": "Legacy table",
+                    "json_metadata": json.dumps(
+                        {
+                            "note": "semantic_view",
+                            "native_filter_configuration": [
+                                {"targets": [{"datasetId": 42}]}
+                            ],
+                        }
+                    ),
+                },
+                {
+                    "id": 735,
+                    "dashboard_title": "Malformed",
+                    "json_metadata": '{"native_filter_configuration": 
"semantic_view"',
+                },
+                {
+                    "id": 737,
+                    "dashboard_title": "Non-dict",
+                    "json_metadata": '"semantic_view"',
+                },
+                {
+                    "id": 739,
+                    "dashboard_title": "Filter and chart",
+                    "json_metadata": metadata("semantic_view", 42),
+                },
+            ],
+        )
+        connection.execute(
+            Dashboard.__table__.insert().values(  # pylint: disable=no-member
+                id=736,
+                dashboard_title="Deleted",
+                json_metadata=metadata("semantic_view", 42),
+                deleted_at=datetime(2026, 1, 1),
+            )
+        )
+        connection.execute(
+            Slice.__table__.insert().values(  # pylint: disable=no-member
+                id=738,
+                slice_name="Member chart",
+                datasource_type="semantic_view",
+                datasource_id=42,
+            )
+        )
+        connection.exec_driver_sql(
+            "INSERT INTO dashboard_slices (dashboard_id, slice_id) VALUES 
(739, 738)"
+        )
+        connection.execute(
+            ReportSchedule.__table__.insert().values(  # pylint: 
disable=no-member
+                id=740,
+                type="Report",
+                name="Native report",
+                crontab="* * * * *",
+                dashboard_id=731,
+                active=True,
+            )
+        )
+        mocker.patch("superset.db.session.scalar", 
side_effect=connection.scalar)
+        mocker.patch("superset.db.session.execute", 
side_effect=connection.execute)
+        mocker.patch("superset.security_manager.can_access", return_value=True)
+        mocker.patch(
+            "superset.security_manager.can_access_all_datasources", 
return_value=True
+        )
+        mocker.patch("superset.security_manager.is_admin", return_value=True)
+
+        total, dependents, inaccessible_count = _dependent_assets(
+            sa.select(SemanticView.id).where(SemanticView.id == 42)
+        )
+        assert total == 5
+        assert inaccessible_count == 0
+        assert {item["name"] for item in dependents} == {
+            "Typed integer",
+            "Typed string",
+            "Filter and chart",
+            "Member chart",
+            "Native report",
+        }
+        dao: MagicMock = mocker.patch(
+            "superset.commands.semantic_layer.delete.SemanticViewDAO"
+        )
+        dao.find_by_id.return_value = MagicMock(id=42)
+        mocker.patch(
+            
"superset.commands.semantic_layer.delete.current_user_can_modify_object",
+            return_value=True,
+        )
+        with pytest.raises(SemanticDeleteDependentsError):
+            DeleteSemanticViewCommand(42).run()
+        dao.delete.assert_not_called()
+
+        mocker.patch(
+            
"superset.commands.semantic_layer.delete.DashboardAccessFilter.apply",
+            return_value=session.query(Dashboard.id).filter(sa.false()),
+        )
+        hidden_total, hidden_dependents, hidden_count = _dependent_assets(
+            sa.select(SemanticView.id).where(SemanticView.id == 42)
+        )
+        assert hidden_total == 5
+        assert hidden_count == 3
+        assert {item["name"] for item in hidden_dependents} == {
+            "Member chart",
+            "Native report",
+        }
+    finally:
+        connection.close()
+
+
[email protected](
+    "config_key", ["native_filter_configuration", "chart_customization_config"]
+)
+def test_semantic_layer_delete_finds_dashboard_target(
+    session: Session, mocker: MockerFixture, config_key: str
+) -> None:
+    """A filter-only or display-control-only dashboard blocks layer 
deletion."""
+    import uuid
+
+    from superset.models.dashboard import Dashboard
+    from superset.models.slice import Slice
+    from superset.semantic_layers.models import SemanticLayer, SemanticView
+    from superset.utils import json
+
+    Slice.metadata.create_all(session.get_bind())  # pylint: disable=no-member
+    connection: Connection = session.get_bind().connect()
+    layer_uuid: uuid.UUID = uuid.uuid4()
+    try:
+        connection.execute(
+            SemanticLayer.__table__.insert().values(  # pylint: 
disable=no-member
+                uuid=layer_uuid, name="Source", type="test"
+            )
+        )
+        connection.execute(
+            SemanticView.__table__.insert().values(  # pylint: 
disable=no-member
+                id=42, name="View", semantic_layer_uuid=layer_uuid
+            )
+        )
+        connection.execute(
+            Dashboard.__table__.insert().values(  # pylint: disable=no-member
+                id=741,
+                dashboard_title="Filter-only",
+                json_metadata=json.dumps(
+                    {
+                        config_key: [
+                            {
+                                "targets": [
+                                    {"datasetId": 42, "datasourceType": 
"semantic_view"}
+                                ]
+                            }
+                        ]
+                    }
+                ),
+            )
+        )
+        mocker.patch("superset.db.session.scalar", 
side_effect=connection.scalar)
+        mocker.patch("superset.db.session.execute", 
side_effect=connection.execute)
+        mocker.patch("superset.security_manager.can_access", return_value=True)
+        mocker.patch(
+            "superset.security_manager.can_access_all_datasources", 
return_value=True
+        )
+        mocker.patch("superset.security_manager.is_admin", return_value=True)
+        dao: MagicMock = mocker.patch(
+            "superset.commands.semantic_layer.delete.SemanticLayerDAO"
+        )
+        model: MagicMock = MagicMock(uuid=layer_uuid)
+        dao.find_by_uuid.return_value = model
+        mocker.patch(
+            
"superset.commands.semantic_layer.delete.current_user_can_modify_object",
+            return_value=True,
+        )
+
+        with pytest.raises(SemanticDeleteDependentsError) as exc_info:
+            DeleteSemanticLayerCommand(str(layer_uuid)).run()
+        assert exc_info.value.total == 1
+        assert exc_info.value.dependents == [
+            {"type": "dashboard", "id": 741, "name": "Filter-only"}
+        ]
+        dao.delete.assert_not_called()
+    finally:
+        connection.close()
+
+
+def test_semantic_delete_skips_json_recursion_error(mocker: MockerFixture) -> 
None:
+    """Invalid stored metadata must not turn a protected delete into a 500."""
+    from superset.commands.semantic_layer.delete import _dashboard_targets_view
+
+    mocker.patch(
+        "superset.commands.semantic_layer.delete.json.loads",
+        side_effect=RecursionError,
+    )
+    assert not _dashboard_targets_view('"semantic_view"', {42}, 741)

Review Comment:
   Neither guard in `_semantic_target_matches` is tested. Dropping the `bool` 
check lets `datasetId: true` block deleting view 1, and `except ValueError` is 
reachable (`int()` rejects decimal strings over 4300 digits, ASCII or not), so 
it should stay. Both mutants pass the suite; this kills each.
   
   ```suggestion
       assert not _dashboard_targets_view('"semantic_view"', {42}, 741)
   
   
   @pytest.mark.parametrize("raw_id", [True, pytest.param("1" * 4301, 
id="long"), "x"])
   def test_semantic_target_rejects_non_ids(raw_id: object) -> None:
       """Bools and over-long decimal strings never match a semantic view."""
       from superset.commands.semantic_layer.delete import 
_semantic_target_matches
   
       target = {"datasourceType": "semantic_view", "datasetId": raw_id}
       assert not _semantic_target_matches(target, {1})
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to