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

rusackas 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 e3bdb3894c0 test(oracle): cover cancel-query against a real running 
statement (#44908)
e3bdb3894c0 is described below

commit e3bdb3894c03bdef9065b9e807ce62738ab759f5
Author: Evan Rusackas <[email protected]>
AuthorDate: Sat Oct 3 13:59:53 2026 -0700

    test(oracle): cover cancel-query against a real running statement (#44908)
    
    Co-authored-by: Claude Sonnet 5 <[email protected]>
---
 .../testcontainers/db_engine_specs/test_oracle.py  | 107 +++++++++++++++++++++
 1 file changed, 107 insertions(+)

diff --git a/tests/testcontainers/db_engine_specs/test_oracle.py 
b/tests/testcontainers/db_engine_specs/test_oracle.py
index 93020613c56..77c00e40167 100644
--- a/tests/testcontainers/db_engine_specs/test_oracle.py
+++ b/tests/testcontainers/db_engine_specs/test_oracle.py
@@ -25,7 +25,10 @@ under 15s measured locally. Almost all the wall-clock cost 
here is the
 image pull itself, same as any other dialect's container.
 """
 
+import threading
+import time
 from collections.abc import Iterator
+from typing import Any
 
 import pytest
 from sqlalchemy import (
@@ -39,6 +42,7 @@ from sqlalchemy import (
 from sqlalchemy.engine import Engine
 
 from superset.db_engine_specs.oracle import OracleEngineSpec
+from superset.models.sql_lab import Query
 from superset.sql.parse import Table
 
 pytestmark = pytest.mark.testcontainers
@@ -93,3 +97,106 @@ def test_get_columns_maps_native_types(engine: Engine) -> 
None:
     for col in by_name.values():
         spec = OracleEngineSpec.get_column_spec(str(col["type"]))
         assert spec is not None
+
+
+def test_cancel_query_stops_a_running_statement(engine: Engine) -> None:
+    """
+    apache/superset#44704 added OracleEngineSpec.get_cancel_query_id/
+    cancel_query, verified only by hand against a real Oracle instance
+    during development (per that PR's own testing notes) -- never pinned
+    down as a repeatable test. This exercises the real pair against a real
+    session: identify the session about to run a long statement on one
+    connection, cancel it from a second connection mid-flight (mirroring
+    SQL Lab's own "new cursor to the db of the query" cancel path), and
+    confirm the first connection's statement actually fails with ORA-01013
+    well before it would complete on its own -- not just that
+    ``cancel_query`` returned ``True`` without effect.
+
+    The container's default user is ``system`` (DBA), which holds the
+    ``ALTER SYSTEM`` privilege ``cancel_query`` needs; see
+    apache/superset#44704's description for the ``False``/ORA-01031 path
+    when that privilege is absent, which this does not exercise.
+    """
+    query_conn = engine.raw_connection()
+    cancel_conn = engine.raw_connection()
+    thread: threading.Thread | None = None
+    try:
+        query_cursor = query_conn.cursor()
+        # get_cancel_query_id runs on the same cursor that is about to
+        # execute the query, before it does -- mirrors SQL Lab's own order.
+        cancel_query_id = OracleEngineSpec.get_cancel_query_id(query_cursor, 
Query())
+        assert cancel_query_id is not None
+
+        outcome: dict[str, Any] = {}
+
+        def run_slow_query() -> None:
+            try:
+                # A tight PL/SQL loop: CPU-bound with O(1) memory, needs no
+                # privileges beyond CREATE SESSION, and (measured directly
+                # against this same image) ~8s at 500M iterations -- plenty
+                # of margin over the cancel, which fires as soon as the
+                # session reports ACTIVE. Two things this is NOT, both tried 
first:
+                #   - `CONNECT BY LEVEL <= n`: materializes the whole
+                #     hierarchy and hits ORA-30009 "not enough memory" long
+                #     before a count this large finishes -- that failure has
+                #     nothing to do with cancellation, so the test would pass
+                #     for the wrong reason.
+                #   - a loop with no loop-carried side effect (`NULL;` as the
+                #     body): optimized away entirely regardless of the
+                #     iteration count, so the statement returns instantly and
+                #     the cancel never has anything to catch. The `cnt`
+                #     assignment here is load-bearing, not cosmetic.
+                query_cursor.execute(
+                    "DECLARE cnt NUMBER := 0; BEGIN "
+                    "FOR i IN 1..500000000 LOOP cnt := cnt + 1; END LOOP; "
+                    "END;"
+                )
+                outcome["completed"] = True
+            except Exception as ex:  # noqa: BLE001  # pylint: 
disable=broad-except
+                outcome["error"] = ex
+
+        thread = threading.Thread(target=run_slow_query, daemon=True)
+        start = time.monotonic()
+        thread.start()
+
+        # Wait until the session is actually executing the statement rather
+        # than sleeping for a fixed time.
+        sid, serial, _instance = cancel_query_id.split(",")
+        cancel_cursor = cancel_conn.cursor()
+        deadline = time.monotonic() + 30
+        while True:
+            cancel_cursor.execute(
+                "SELECT status FROM v$session "  # noqa: S608
+                f"WHERE sid = {int(sid)} AND serial# = {int(serial)}"
+            )
+            row = cancel_cursor.fetchone()
+            if row is not None and row[0] == "ACTIVE":
+                break
+            assert thread.is_alive(), "the statement ended before it could be 
cancelled"
+            assert time.monotonic() < deadline, "the statement never became 
ACTIVE"
+            time.sleep(0.1)
+
+        cancelled = OracleEngineSpec.cancel_query(
+            cancel_cursor, Query(), cancel_query_id
+        )
+        assert cancelled is True
+
+        thread.join(timeout=60)
+        elapsed = time.monotonic() - start
+
+        assert not thread.is_alive(), (
+            "cancel_query returned True but the statement is still running"
+        )
+        assert "error" in outcome, "the statement completed instead of being 
cancelled"
+        assert "ORA-01013" in str(outcome["error"])
+        # The cancel took effect promptly, not "eventually" after the slow
+        # statement would have finished on its own regardless.
+        assert elapsed < 30
+    finally:
+        # Make sure the worker has finished before closing its connection, even
+        # when an assertion above failed; a still-running worker would 
otherwise
+        # race the close or hang pytest at exit.
+        if thread is not None and thread.is_alive():
+            thread.join(timeout=60)
+        query_conn.close()
+        cancel_conn.close()

Reply via email to