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


##########
superset/sql_lab.py:
##########
@@ -628,8 +651,12 @@ def execute_sql_statements(  # noqa: C901
                 return payload
 
         # Commit the connection so CTA queries will create the table and any 
DML.
+        committed = False
         if parsed_script.has_mutation() or query.select_as_cta:

Review Comment:
   Thanks, good catch. Fixed in dd5670cc8185700a68384ee8171ae0940c64a20c: the 
sync path now reads the status right before `conn.commit()`, like 
`_execute_sql_statements`. A stop recorded while the final statement runs skips 
the commit, so the transaction is dropped with the connection and the query 
ends STOPPED with no rows written. Only a stop that lands during or after the 
commit loses to the committed work and ends SUCCESS.
   
   Regression test: 
`tests/unit_tests/sql_lab_test.py::test_get_sql_results_stop_before_target_commit_stays_stopped`
 (mutation and CTAS cases) commits a stop from another session while the last 
statement runs. It fails at d117709fde (commit runs, query ends SUCCESS) and 
passes now. The finish-before-stop case is 
`test_get_sql_results_keeps_committed_work_when_stop_races_commit`, which still 
passes (stop during the commit ends SUCCESS).
   
   Siblings checked:
   - superset/sql/execution/celery_task.py:605 — not affected: the Celery path 
already checks for a stop before its target commit and skips it.
   - superset/sql_lab.py:608 — not affected: the per-block stop check runs 
before each statement starts, so it cannot see a stop recorded during the last 
one.
   - superset/sql_lab.py:707 — not affected: the post-execution check still 
ends STOPPED unless the target commit ran.
   - superset/sql_lab.py:815 — not affected: the results-backend write-failure 
check still ends STOPPED unless the target commit ran.
   
   Remaining: engines that autocommit each statement (Impala, Hive, Trino) make 
the rows permanent when the statement finishes and `conn.commit()` does 
nothing, so a stop there ends STOPPED with the rows already written. This is 
the same on both paths.
   



##########
superset/sql/execution/celery_task.py:
##########
@@ -198,102 +218,110 @@ def _finalize_successful_query(
                 }
             )
 
-    query.rows = total_rows
-    query.progress = 100
-    query.set_extra_json_key("progress", None)
-    # Store columns from last statement (for compatibility)
-    if execution_results and execution_results[-1][1] is not None:
-        query.set_extra_json_key("columns", execution_results[-1][1].columns)
-    query.end_time = now_as_float()
-
-    payload.update(
-        {
-            "status": QueryStatus.SUCCESS.value,
-            "statements": statements_data,
-            "total_execution_time_ms": total_execution_time_ms,
-            "query": query.to_dict(),
-        }
-    )
-    payload["query"]["state"] = QueryStatus.SUCCESS.value
+    with db.session.no_autoflush:
+        query.rows = total_rows
+        query.progress = 100
+        query.set_extra_json_key("progress", None)
+        # Store columns from last statement (for compatibility)
+        if execution_results and execution_results[-1][1] is not None:
+            query.set_extra_json_key("columns", 
execution_results[-1][1].columns)
+        query.end_time = now_as_float()
+
+        payload.update(
+            {
+                "status": QueryStatus.SUCCESS.value,
+                "statements": statements_data,
+                "total_execution_time_ms": total_execution_time_ms,
+                "query": query.to_dict(),
+            }
+        )
+        payload["query"]["state"] = QueryStatus.SUCCESS.value
 
 
 def _store_results_in_backend(
     query: Query,
     payload: dict[str, Any],
     database: Any,
+    durable: bool = False,
 ) -> None:
     """Store query results in the results backend."""
-    key = str(uuid.uuid4())
-    payload["query"]["resultsKey"] = key
-    logger.info(
-        "Query %s: Storing results in results backend, key: %s",
-        str(query.id),
-        key,
-    )
-    stats_logger = app.config["STATS_LOGGER"]
-    with stats_timing("sqllab.query.results_backend_write", stats_logger):
-        with stats_timing(
-            "sqllab.query.results_backend_write_serialization", stats_logger
-        ):
-            serialized_payload = _serialize_payload(payload)
-
-            # Check payload size limit
-            if sql_lab_payload_max_mb := 
app.config.get("SQLLAB_PAYLOAD_MAX_MB"):
-                serialized_payload_size = len(serialized_payload)
-                max_bytes = sql_lab_payload_max_mb * BYTES_IN_MB
-
-                if serialized_payload_size > max_bytes:
-                    logger.info("Result size exceeds the allowed limit.")
-                    raise SupersetErrorException(
-                        SupersetError(
-                            message=(
-                                f"Result size "
-                                f"({serialized_payload_size / BYTES_IN_MB:.2f} 
MB) "
-                                f"exceeds the allowed limit of "
-                                f"{sql_lab_payload_max_mb} MB."
-                            ),
-                            
error_type=SupersetErrorType.RESULT_TOO_LARGE_ERROR,
-                            level=ErrorLevel.ERROR,
+    with db.session.no_autoflush:
+        key = str(uuid.uuid4())
+        payload["query"]["resultsKey"] = key
+        logger.info(
+            "Query %s: Storing results in results backend, key: %s",
+            str(query.id),
+            key,
+        )
+        stats_logger = app.config["STATS_LOGGER"]
+        with stats_timing("sqllab.query.results_backend_write", stats_logger):
+            with stats_timing(
+                "sqllab.query.results_backend_write_serialization", 
stats_logger
+            ):
+                serialized_payload = _serialize_payload(payload)
+
+                # Check payload size limit
+                if sql_lab_payload_max_mb := 
app.config.get("SQLLAB_PAYLOAD_MAX_MB"):
+                    serialized_payload_size = len(serialized_payload)
+                    max_bytes = sql_lab_payload_max_mb * BYTES_IN_MB
+
+                    if serialized_payload_size > max_bytes:
+                        logger.info("Result size exceeds the allowed limit.")
+                        raise SupersetErrorException(
+                            SupersetError(
+                                message=(
+                                    f"Result size "
+                                    f"({serialized_payload_size / 
BYTES_IN_MB:.2f} MB) "
+                                    f"exceeds the allowed limit of "
+                                    f"{sql_lab_payload_max_mb} MB."
+                                ),
+                                
error_type=SupersetErrorType.RESULT_TOO_LARGE_ERROR,
+                                level=ErrorLevel.ERROR,
+                            )
                         )
-                    )
-
-        cache_timeout = database.cache_timeout
-        if cache_timeout is None:
-            cache_timeout = app.config["CACHE_DEFAULT_TIMEOUT"]
 
-        compressed = zlib_compress(serialized_payload)
-        logger.debug("*** serialized payload size: %i", 
len(serialized_payload))
-        logger.debug("*** compressed payload size: %i", len(compressed))
-
-        write_success = results_backend.set(key, compressed, cache_timeout)
-        if not write_success:
-            logger.error(
-                "Query %s: Failed to store results in backend, key: %s",
-                str(query.id),
-                key,
-            )
-            stats_logger.incr("sqllab.results_backend.write_failure")
-            query.results_key = None
-            query.status = QueryStatus.FAILED
-            query.error_message = (
-                "Failed to store query results in the results backend. "
-                "Please try again or contact your administrator."
-            )
-            db.session.commit()  # pylint: disable=consider-using-transaction
-            raise SupersetErrorException(
-                SupersetError(
-                    message=__("Failed to store query results. Please try 
again."),
-                    error_type=SupersetErrorType.RESULTS_BACKEND_ERROR,
-                    level=ErrorLevel.ERROR,
+            cache_timeout = database.cache_timeout
+            if cache_timeout is None:
+                cache_timeout = app.config["CACHE_DEFAULT_TIMEOUT"]
+
+            compressed = zlib_compress(serialized_payload)
+            logger.debug("*** serialized payload size: %i", 
len(serialized_payload))
+            logger.debug("*** compressed payload size: %i", len(compressed))
+
+            write_success = results_backend.set(key, compressed, cache_timeout)
+            if not durable and _make_check_stopped_fn(query, 
preserve_pending=True)():

Review Comment:
   Thanks, I took it. Fixed in ba1fcc6b893a15a33f36c33fec060274b41e840c: when 
this check fires after a successful write, the task now calls 
`results_backend.delete(key)` before returning. The same happens when the stop 
is caught later, at the publish check or when the guarded publish commit finds 
the row STOPPED, since the blob is stranded the same way there. If the delete 
raises or returns false, it is logged as a warning and the query still ends 
STOPPED. A failed write stores nothing, so nothing is deleted.
   
   Regression test: 
`tests/unit_tests/sql/execution/test_celery_task.py::test_stop_after_results_write_deletes_stored_results`
 commits a stop from another session during the write, after the post-write 
check, and after the publish check, with a working and a failing delete. The 
six stop-after-successful-write cases fail at 
dd5670cc8185700a68384ee8171ae0940c64a20c (delete never called, blob left in the 
backend) and pass now. The no-stop and failed-write cases pass on both and 
check that nothing is deleted.
   
   Siblings checked:
   - superset/sql/execution/celery_task.py:654 — fixed in 
ba1fcc6b893a15a33f36c33fec060274b41e840c (stop caught at the publish check 
after a successful write).
   - superset/sql/execution/celery_task.py:660 — fixed in 
ba1fcc6b893a15a33f36c33fec060274b41e840c (guarded publish commit finds the row 
STOPPED).
   - superset/sql/execution/celery_task.py:296 — not affected: the 
write-failure branch stored nothing.
   - superset/sql_lab.py:784 — not affected: the legacy path has no stop check 
between a successful write and its final commit. A stop during the write keeps 
the row STOPPED and commits `results_key`, so the blob is still referenced by 
the query, not stranded. That is unchanged from master.
   
   Remaining: an exception after a successful write that is not a stop (for 
example the metadata commit in the publish step failing) ends FAILED and still 
leaves the blob until `cache_timeout`. This PR does not change that.
   



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