88from typing import (
99 TYPE_CHECKING ,
1010 Any ,
11+ Dict ,
1112 Iterator ,
1213 List ,
1314 Optional ,
3940 UPDATE_PARAMETERS_HEADER ,
4041 BaseCursor ,
4142 CursorState ,
42- RowSet ,
4343 _parse_update_endpoint ,
4444 _parse_update_parameters ,
4545 _raise_if_internal_set_parameter ,
46+ async_not_allowed ,
4647 check_not_closed ,
4748 check_query_executed ,
4849)
4950from firebolt .utils .exception import (
5051 EngineNotRunningError ,
5152 FireboltDatabaseError ,
53+ FireboltError ,
54+ NotSupportedError ,
5255 OperationalError ,
5356 ProgrammingError ,
5457 QueryTimeoutError ,
@@ -186,53 +189,93 @@ async def _parse_response_headers(self, headers: Headers) -> None:
186189 param_dict = _parse_update_parameters (headers .get (UPDATE_PARAMETERS_HEADER ))
187190 self ._update_set_parameters (param_dict )
188191
192+ @abstractmethod
193+ async def execute_async (
194+ self ,
195+ query : str ,
196+ parameters : Optional [Sequence [ParameterType ]] = None ,
197+ skip_parsing : bool = False ,
198+ ) -> int :
199+ """Execute a database query without maintaining a connection."""
200+ ...
201+
189202 async def _do_execute (
190203 self ,
191204 raw_query : str ,
192205 parameters : Sequence [Sequence [ParameterType ]],
193206 skip_parsing : bool = False ,
194207 timeout : Optional [float ] = None ,
208+ async_execution : bool = False ,
195209 ) -> None :
196210 self ._reset ()
197- # Allow users to manually skip parsing for performance improvement.
198211 queries : List [Union [SetParameter , str ]] = (
199212 [raw_query ] if skip_parsing else split_format_sql (raw_query , parameters )
200213 )
201214 timeout_controller = TimeoutController (timeout )
215+
216+ if len (queries ) > 1 and async_execution :
217+ raise FireboltError (
218+ "Server side async does not support multi-statement queries"
219+ )
202220 try :
203221 for query in queries :
204- start_time = time .time ()
205- Cursor ._log_query (query )
206- timeout_controller .raise_if_timeout ()
207-
208- if isinstance (query , SetParameter ):
209- row_set : RowSet = (- 1 , None , None , None )
210- await self ._validate_set_parameter (
211- query , timeout_controller .remaining ()
212- )
213- else :
214- resp = await self ._api_request (
215- query ,
216- {"output_format" : JSON_OUTPUT_FORMAT },
217- timeout = timeout_controller .remaining (),
218- )
219- await self ._raise_if_error (resp )
220- await self ._parse_response_headers (resp .headers )
221- row_set = self ._row_set_from_response (resp )
222-
223- self ._append_row_set (row_set )
224-
225- logger .info (
226- f"Query fetched { self .rowcount } rows in"
227- f" { time .time () - start_time } seconds."
222+ await self ._execute_single_query (
223+ query , timeout_controller , async_execution
228224 )
229-
230225 self ._state = CursorState .DONE
231-
232226 except Exception :
233227 self ._state = CursorState .ERROR
234228 raise
235229
230+ async def _execute_single_query (
231+ self ,
232+ query : Union [SetParameter , str ],
233+ timeout_controller : TimeoutController ,
234+ async_execution : bool ,
235+ ) -> None :
236+ start_time = time .time ()
237+ Cursor ._log_query (query )
238+ timeout_controller .raise_if_timeout ()
239+
240+ if isinstance (query , SetParameter ):
241+ if async_execution :
242+ raise FireboltError (
243+ "Server side async does not support set statements, "
244+ "please use execute to set this parameter"
245+ )
246+ await self ._validate_set_parameter (query , timeout_controller .remaining ())
247+ else :
248+ await self ._handle_query_execution (
249+ query , timeout_controller , async_execution
250+ )
251+
252+ if not async_execution :
253+ logger .info (
254+ f"Query fetched { self .rowcount } rows in"
255+ f" { time .time () - start_time } seconds."
256+ )
257+ else :
258+ logger .info ("Query submitted for async execution." )
259+
260+ async def _handle_query_execution (
261+ self , query : str , timeout_controller : TimeoutController , async_execution : bool
262+ ) -> None :
263+ query_params : Dict [str , Any ] = {"output_format" : JSON_OUTPUT_FORMAT }
264+ if async_execution :
265+ query_params ["async" ] = True
266+ resp = await self ._api_request (
267+ query ,
268+ query_params ,
269+ timeout = timeout_controller .remaining (),
270+ )
271+ await self ._raise_if_error (resp )
272+ if async_execution :
273+ self ._parse_async_response (resp )
274+ else :
275+ await self ._parse_response_headers (resp .headers )
276+ row_set = self ._row_set_from_response (resp )
277+ self ._append_row_set (row_set )
278+
236279 @check_not_closed
237280 async def execute (
238281 self ,
@@ -346,6 +389,7 @@ async def nextset(self) -> None:
346389
347390 # Iteration support
348391 @check_not_closed
392+ @async_not_allowed
349393 @check_query_executed
350394 def __aiter__ (self ) -> Cursor :
351395 return self
@@ -373,6 +417,7 @@ async def __aexit__(
373417 self .close ()
374418
375419 @check_not_closed
420+ @async_not_allowed
376421 @check_query_executed
377422 async def __anext__ (self ) -> List [ColType ]:
378423 row = await self .fetchone ()
@@ -392,6 +437,47 @@ def __init__(
392437 assert isinstance (client , AsyncClientV2 )
393438 super ().__init__ (* args , client = client , connection = connection , ** kwargs )
394439
440+ @check_not_closed
441+ async def execute_async (
442+ self ,
443+ query : str ,
444+ parameters : Optional [Sequence [ParameterType ]] = None ,
445+ skip_parsing : bool = False ,
446+ ) -> int :
447+ """
448+ Execute a database query without maintating a connection.
449+
450+ Supported features:
451+ Parameterized queries: placeholder characters ('?') are substituted
452+ with values provided in `parameters`. Values are formatted to
453+ be properly recognized by database and to exclude SQL injection.
454+
455+ Not supported:
456+ Multi-statement queries: multiple statements, provided in a single query
457+ and separated by semicolon.
458+ SET statements: to provide additional query execution parameters, execute
459+ `SET param=value` statement before it. Use `execute` method to set
460+ parameters.
461+
462+ Args:
463+ query (str): SQL query to execute
464+ parameters (Optional[Sequence[ParameterType]]): A sequence of substitution
465+ parameters. Used to replace '?' placeholders inside a query with
466+ actual values
467+ skip_parsing (bool): Flag to disable query parsing. This will
468+ disable parameterized queries while potentially improving performance
469+
470+ Returns:
471+ int: Always returns -1, as async execution does not return row count.
472+ """
473+ await self ._do_execute (
474+ query ,
475+ [parameters ] if parameters else [],
476+ skip_parsing ,
477+ async_execution = True ,
478+ )
479+ return - 1
480+
395481 async def is_db_available (self , database_name : str ) -> bool :
396482 """
397483 Verify that the database exists.
@@ -468,3 +554,13 @@ async def _filter_request(self, endpoint: str, filters: dict) -> Response:
468554 )
469555 resp .raise_for_status ()
470556 return resp
557+
558+ async def execute_async (
559+ self ,
560+ query : str ,
561+ parameters : Optional [Sequence [ParameterType ]] = None ,
562+ skip_parsing : bool = False ,
563+ ) -> int :
564+ raise NotSupportedError (
565+ "Async execution is not supported in this version " " of Firebolt."
566+ )
0 commit comments