codeant-ai-for-open-source[bot] commented on code in PR #39977:
URL: https://github.com/apache/superset/pull/39977#discussion_r3220689675


##########
superset/models/helpers.py:
##########
@@ -654,7 +656,94 @@ def created_on_humanized(self) -> str:
 
     @renders("changed_on")
     def modified(self) -> Markup:
-        return Markup(f'<span 
class="no-wrap">{self.changed_on_humanized}</span>')
+        return Markup(f'<span 
class="no-wrap">{self.changed_on_humanized}</span>')  # noqa: S704
+
+
+SKIP_VISIBILITY_FILTER = "skip_visibility_filter"
+
+
+class SoftDeleteMixin:
+    """Mixin that adds soft-delete support to a SQLAlchemy model.
+
+    Adds a nullable ``deleted_at`` column. When set, the row is treated as
+    deleted and excluded from standard ORM queries via a global
+    ``do_orm_execute`` listener registered at app init.
+
+    Delete commands should call ``soft_delete()`` to mark the object as
+    deleted.  Use ``BaseDAO.delete()`` (which calls ``session.delete()``)
+    for permanent hard deletion.
+
+    See also: ``_add_soft_delete_filter`` (the listener function) and
+    ``SKIP_VISIBILITY_FILTER`` (the execution-option key used to opt out
+    of the filter in restore commands and admin tooling).
+    """
+
+    deleted_at = sa.Column(sa.DateTime, nullable=True, index=True)
+
+    @hybrid_property
+    def is_deleted(self) -> bool:
+        return self.deleted_at is not None
+
+    @is_deleted.expression  # type: ignore
+    def is_deleted(cls) -> ColumnElement:  # noqa: N805
+        return cls.deleted_at.is_not(None)
+
+    @classmethod
+    def not_deleted(cls) -> ColumnElement:
+        """Filter clause for active (non-deleted) rows."""
+        return cls.deleted_at.is_(None)
+
+    def soft_delete(self) -> None:
+        """Mark this object as soft-deleted."""
+        # Naive datetime, mirroring AuditMixinNullable.changed_on. PR #33693
+        # reverted a UTC migration on the audit columns; if/when those move
+        # to UTC-aware, this assignment should follow.
+        self.deleted_at = datetime.now()
+
+    def restore(self) -> None:
+        """Clear the soft-delete marker, making this object active again."""
+        self.deleted_at = None
+
+
+def _add_soft_delete_filter(execute_state: ORMExecuteState) -> None:
+    """Global ``do_orm_execute`` listener that automatically excludes
+    soft-deleted rows from every ORM SELECT.
+
+    Uses SQLAlchemy's recommended soft-delete pattern
+    (``do_orm_execute`` + ``with_loader_criteria``).
+
+    Two opt-out paths:
+
+    * **Per-query** — ``execution_options(skip_visibility_filter=True)``.
+      The narrow tool. Use this from any non-user-facing code path
+      (background jobs, import pipelines, internal admin tools) that
+      needs to query soft-deleted rows.
+    * **Per-request** — ``flask.g.skip_visibility_filter = True``.
+      The broad tool. Reserved for user-facing list endpoints whose
+      rison filter (``*_deleted_state=include|only``) explicitly asks
+      to surface soft-deleted rows for the rest of that request.
+      Anything else needing to bypass the filter should use the
+      per-query option, since the request-scoped flag also affects
+      any incidental query inside the same request.
+    """
+    skip_visibility_filter = execute_state.execution_options.get(
+        SKIP_VISIBILITY_FILTER, False
+    )
+    try:
+        skip_visibility_filter = skip_visibility_filter or getattr(
+            g, SKIP_VISIBILITY_FILTER, False
+        )
+    except RuntimeError:
+        pass
+
+    if execute_state.is_select and not skip_visibility_filter:
+        execute_state.statement = execute_state.statement.options(
+            with_loader_criteria(
+                SoftDeleteMixin,
+                lambda cls: cls.deleted_at.is_(None),
+                include_aliases=True,
+            )
+        )

Review Comment:
   **Suggestion:** The global `do_orm_execute` hook applies 
`with_loader_criteria` to every SELECT, including relationship and column 
loader queries; SQLAlchemy's recommended pattern excludes those loader paths to 
avoid repeatedly stacking the same predicate. Without those guards, lazy/eager 
relationship loads can accumulate redundant `deleted_at IS NULL` clauses and 
increase query complexity/latency. Gate this block with `not 
execute_state.is_column_load` and `not execute_state.is_relationship_load`. 
[performance]
   
   <details>
   <summary><b>Severity Level:</b> Major ⚠️</summary>
   
   ```mdx
   - ⚠️ Soft-deletable model queries gain redundant soft-delete predicates.
   - ⚠️ Relationship loads perform extra filtering work per lazy load.
   - ⚠️ High-traffic chart and dashboard APIs may slow slightly.
   ```
   </details>
   <details>
   <summary><b>Steps of Reproduction ✅ </b></summary>
   
   ```mdx
   1. At app initialization, 
`SupersetAppInitializer.setup_soft_delete_listener()` (added in
   this PR at `superset/initialization/__init__.py:770-771`) registers
   `_add_soft_delete_filter` from `superset/models/helpers.py:708-746` as a 
`do_orm_execute`
   listener on SQLAlchemy `Session`, so it runs on every ORM statement.
   
   2. `_add_soft_delete_filter()` computes `skip_visibility_filter` from
   `execute_state.execution_options` and `flask.g` at 
`superset/models/helpers.py:729-735`,
   then unconditionally applies `with_loader_criteria(SoftDeleteMixin, ...)` 
for any
   `execute_state.is_select` where `skip_visibility_filter` is false at lines 
739-746.
   
   3. SQLAlchemy's documented best-practice pattern for this hook (referenced 
in the
   docstring at `superset/models/helpers.py:712-713`) includes additional 
guards `not
   execute_state.is_column_load` and `not execute_state.is_relationship_load` 
to avoid
   reapplying identical `with_loader_criteria` options on per-column and 
per-relationship
   loader SELECTs; those guards are currently missing in this implementation.
   
   4. Once real Superset entities (e.g. charts, dashboards, datasets) adopt 
`SoftDeleteMixin`
   in their respective rollout PRs as described in the SIP-208 summary, any ORM 
operation
   that performs lazy or eager relationship loading for those models (for 
example, API
   handlers like `ChartRestApi` and `DashboardRestApi` registered in 
`init_views()` at
   `superset/initialization/__init__.py:154-269`) will trigger `do_orm_execute` 
both for the
   primary SELECT and for each relationship/column load; 
`_add_soft_delete_filter()` will
   reapply `with_loader_criteria` on each of those loader queries, stacking 
redundant
   `deleted_at IS NULL` criteria and increasing query complexity and latency 
compared to the
   recommended guarded pattern.
   ```
   </details>
   
   [Fix in 
Cursor](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset%2Fmodels%2Fhelpers.py%0A%2A%2ALine%3A%2A%2A%20739%3A746%0A%2A%2AComment%3A%2A%2A%0A%09%2APerformance%3A%20The%20global%20%60do_orm_execute%60%20hook%20applies%20%60with_loader_criteria%60%20to%20every%20SELECT%2C%20including%20relationship%20and%20column%20loader%20queries%3B%20SQLAlchemy%27s%20recommended%20pattern%20excludes%20those%20loader%20paths%20to%20avoid%20repeatedly%20stacking%20the%20same%20predicate.%20Without%20those%20guards%2C%20lazy%2Feager%20relationship%20loads%20can%20accumulate%20redundant%20%60deleted_at%20IS%20NULL%60%20clauses%20and%20increase%20query%20complexity%2Flatency.%20Gate%20this%20block%20with%20%60not%20execute_state.is_column_load%60%20and%20%60not%20execute_state.is_relationship_load%60.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%2
 
0I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0AOnce%20fix%20is%20implemented%2C%20also%20check%20other%20comments%20on%20the%20same%20PR%2C%20and%20ask%20user%20if%20the%20user%20wants%20to%20fix%20the%20rest%20of%20the%20comments%20as%20well.%20if%20said%20yes%2C%20then%20fetch%20all%20the%20comments%20validate%20the%20correctness%20and%20implement%20a%20minimal%20fix%0A)
 | [Fix in VSCode 
Claude](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt=This%20is%20a%20comment%20left%20during%20a%20code%20review.%0A%0A%2A%2APath%3A%2A%2A%20superset%2Fmodels%2Fhelpers.py%0A%2A%2ALine%3A%2A%2A%20739%3A746%0A%2A%2AComment%3A%2A%2A%0A%09%2APerformance%3A%20The%20global%20%60do_orm_execute%60%20hook%20applies%20%60with_loader_criteria%60%20to%20every%20SELECT%2C%20including%20relationship%20and%20column%20loader%20queries%3B%20SQLAlchemy%27s%20recommended%20pattern%20excludes%20those%20loader%20paths%20to%20avoid%20rep
 
eatedly%20stacking%20the%20same%20predicate.%20Without%20those%20guards%2C%20lazy%2Feager%20relationship%20loads%20can%20accumulate%20redundant%20%60deleted_at%20IS%20NULL%60%20clauses%20and%20increase%20query%20complexity%2Flatency.%20Gate%20this%20block%20with%20%60not%20execute_state.is_column_load%60%20and%20%60not%20execute_state.is_relationship_load%60.%0A%0AValidate%20the%20correctness%20of%20the%20flagged%20issue.%20If%20correct%2C%20How%20can%20I%20resolve%20this%3F%20If%20you%20propose%20a%20fix%2C%20implement%20it%20and%20please%20make%20it%20concise.%0AOnce%20fix%20is%20implemented%2C%20also%20check%20other%20comments%20on%20the%20same%20PR%2C%20and%20ask%20user%20if%20the%20user%20wants%20to%20fix%20the%20rest%20of%20the%20comments%20as%20well.%20if%20said%20yes%2C%20then%20fetch%20all%20the%20comments%20validate%20the%20correctness%20and%20implement%20a%20minimal%20fix%0A)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/models/helpers.py
   **Line:** 739:746
   **Comment:**
        *Performance: The global `do_orm_execute` hook applies 
`with_loader_criteria` to every SELECT, including relationship and column 
loader queries; SQLAlchemy's recommended pattern excludes those loader paths to 
avoid repeatedly stacking the same predicate. Without those guards, lazy/eager 
relationship loads can accumulate redundant `deleted_at IS NULL` clauses and 
increase query complexity/latency. Gate this block with `not 
execute_state.is_column_load` and `not execute_state.is_relationship_load`.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39977&comment_hash=d180fe9bf0502362a0a16b59f634fb4edb8d854a07eacdb05f51bbae773a68ca&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F39977&comment_hash=d180fe9bf0502362a0a16b59f634fb4edb8d854a07eacdb05f51bbae773a68ca&reaction=dislike'>👎</a>



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