You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by en...@apache.org on 2026/10/07 12:51:12 UTC

(superset) branch master updated: fix(semantic): map layer views as a collection (#44902)

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

EnxDev 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 90b0a1a9160 fix(semantic): map layer views as a collection (#44902)
90b0a1a9160 is described below

commit 90b0a1a9160e742e16bc79fc79c076262242aee2
Author: Mike Bridge <mi...@preset.io>
AuthorDate: Wed Oct 7 06:51:03 2026 -0600

    fix(semantic): map layer views as a collection (#44902)
---
 superset/semantic_layers/models.py              |  4 +-
 tests/unit_tests/semantic_layers/models_test.py | 54 ++++++++++++++++++++++++-
 2 files changed, 54 insertions(+), 4 deletions(-)

diff --git a/superset/semantic_layers/models.py b/superset/semantic_layers/models.py
index ee97ff22855..d17cef6de62 100644
--- a/superset/semantic_layers/models.py
+++ b/superset/semantic_layers/models.py
@@ -34,7 +34,7 @@ from flask_appbuilder import Model
 from flask_babel import lazy_gettext as _
 from sqlalchemy import Column, ForeignKey, Integer, String, Text
 from sqlalchemy.engine.base import Connection
-from sqlalchemy.orm import relationship
+from sqlalchemy.orm import Mapped, relationship
 from sqlalchemy.orm.mapper import Mapper
 from sqlalchemy_utils import UUIDType
 from sqlalchemy_utils.types.json import JSONType
@@ -193,7 +193,7 @@ class SemanticLayer(AuditMixinNullable, Model):
     perm = Column(String(1000), nullable=True)
 
     # Semantic views relationship
-    semantic_views: list[SemanticView] = relationship(
+    semantic_views: Mapped[list[SemanticView]] = relationship(
         "SemanticView",
         back_populates="semantic_layer",
         cascade="all, delete-orphan",
diff --git a/tests/unit_tests/semantic_layers/models_test.py b/tests/unit_tests/semantic_layers/models_test.py
index 545077410c2..4306ba4aff0 100644
--- a/tests/unit_tests/semantic_layers/models_test.py
+++ b/tests/unit_tests/semantic_layers/models_test.py
@@ -20,11 +20,13 @@
 from __future__ import annotations
 
 import uuid
-from typing import Any
+from typing import Any, cast
 from unittest.mock import MagicMock, patch
 
 import pyarrow as pa
 import pytest
+from sqlalchemy import inspect, select
+from sqlalchemy.engine import Connection, Engine
 from sqlalchemy.orm import Session
 from superset_core.semantic_layers.types import (
     Dimension,
@@ -1224,6 +1226,55 @@ def test_semantic_view_get_compatible_dimensions(
 # =============================================================================
 
 
+def test_semantic_layer_loads_all_semantic_views(session: Session) -> None:
+    """A reloaded layer exposes every stored view as a collection."""
+    assert inspect(SemanticLayer).relationships.semantic_views.uselist is True
+    SemanticView.metadata.create_all(session.get_bind())
+    layer: SemanticLayer = SemanticLayer(
+        uuid=uuid.uuid4(), name="Orders", type="test", configuration="{}"
+    )
+    views: list[SemanticView] = [
+        SemanticView(name=name, semantic_layer=layer, configuration="{}")
+        for name in ("Daily", "Monthly")
+    ]
+    session.add_all([layer, *views])
+    session.flush()
+    session.expire(layer, ["semantic_views"])
+
+    assert isinstance(layer.semantic_views, list)
+    assert {view.name for view in layer.semantic_views} == {"Daily", "Monthly"}
+
+
+@pytest.mark.parametrize("load_before_delete", [True, False])
+def test_semantic_layer_delete_removes_multiple_views(
+    session: Session, load_before_delete: bool
+) -> None:
+    """Loaded and unloaded relationships delete all persisted child rows."""
+    engine: Engine = cast(Engine, session.get_bind())
+    connection: Connection
+    with engine.connect() as connection:
+        connection.exec_driver_sql("PRAGMA foreign_keys=ON")
+    SemanticView.metadata.create_all(engine)
+    layer: SemanticLayer = SemanticLayer(
+        uuid=uuid.uuid4(), name="Orders", type="test", configuration="{}"
+    )
+    views: list[SemanticView] = [
+        SemanticView(name=name, semantic_layer=layer, configuration="{}")
+        for name in ("Daily", "Monthly")
+    ]
+    session.add_all([layer, *views])
+    session.flush()
+    view_ids: set[int] = {view.id for view in views}
+    session.expire(layer, ["semantic_views"])
+    if load_before_delete:
+        assert {view.id for view in layer.semantic_views} == view_ids
+
+    session.delete(layer)
+    session.flush()
+
+    assert session.scalars(select(SemanticView.id)).all() == []
+
+
 def test_semantic_view_compatible_dimensions_collapse_grains(
     mock_implementation: MagicMock,
 ) -> None:
@@ -2486,7 +2537,6 @@ def test_layer_delete_removes_child_view_permissions(
 def test_layer_delete_batches_permission_ownership_queries(session: Any) -> None:
     """The unloaded layer hook batches child permission ownership checks."""
     from sqlalchemy import event, inspect
-    from sqlalchemy.engine import Connection
 
     from superset import security_manager