@@ -342,6 +342,38 @@ def test_execute_async__large_result(self, extra_params):
342342
343343 assert len (result ) == x_dimension * y_dimension
344344
345+ @pytest .mark .parametrize (
346+ "extra_params" ,
347+ [
348+ {},
349+ ],
350+ )
351+ def test_execute_async__close_without_fetch_frees_handle (self , extra_params ):
352+ """Closing a cursor whose async command result was never fetched must free
353+ the server-side statement handle (issue #791). Otherwise the handle leaks
354+ until the session closes."""
355+ with self .cursor (extra_params ) as cursor :
356+ cursor .execute_async ("SELECT 1" )
357+
358+ # Capture the server-side command id before we close the cursor.
359+ command_id = cursor .active_command_id
360+ assert command_id is not None
361+
362+ backend = cursor .backend
363+
364+ # Sanity: the handle is live and pollable before close.
365+ backend .get_query_state (command_id )
366+
367+ # User decides not to wait for the result and closes the cursor
368+ # without ever calling get_async_execution_result().
369+ cursor .close ()
370+
371+ # After close, the server-side handle must have been freed, so a
372+ # re-poll of the saved command id should raise a server error.
373+ # Pre-fix (leak): this poll succeeds. Post-fix: it raises.
374+ with pytest .raises ((RequestError , OperationalError , DatabaseError )):
375+ backend .get_query_state (command_id )
376+
345377
346378# Exclude Retry tests because they require specific setups, and LargeQueries too slow for core
347379# tests
0 commit comments