1010from databricks .sql import USER_AGENT_NAME , __version__
1111from databricks .sql import *
1212from databricks .sql .thrift_backend import ThriftBackend
13- from databricks .sql .utils import ExecuteResponse
13+ from databricks .sql .utils import ExecuteResponse , ParamEscaper
1414
1515logger = logging .getLogger (__name__ )
1616
@@ -129,6 +129,13 @@ def close(self) -> None:
129129 for cursor in self ._cursors :
130130 cursor .close ()
131131
132+ def commit (self ):
133+ """No-op because Databricks does not support transactions"""
134+ pass
135+
136+ def rollback (self ):
137+ raise NotSupportedError ("Transactions are not supported on Databricks" )
138+
132139
133140class Cursor :
134141 def __init__ (self ,
@@ -144,7 +151,7 @@ def __init__(self,
144151 visible by other cursors or connections.
145152 """
146153 self .connection = connection
147- self .rowcount = - 1
154+ self .rowcount = - 1 # Return -1 as this is not supported
148155 self .buffer_size_bytes = result_buffer_size_bytes
149156 self .active_result_set = None
150157 self .arraysize = arraysize
@@ -153,6 +160,8 @@ def __init__(self,
153160 self .executing_command_id = None
154161 self .thrift_backend = thrift_backend
155162 self .active_op_handle = None
163+ self .escaper = ParamEscaper ()
164+ self .lastrowid = None
156165
157166 def __enter__ (self ):
158167 return self
@@ -178,26 +187,23 @@ def _check_not_closed(self):
178187 if not self .open :
179188 raise Error ("Attempting operation on closed cursor" )
180189
181- def execute (self ,
182- operation : str ,
183- query_params : Optional [Dict [str , str ]] = None ,
184- metadata : Optional [Dict [str , str ]] = None ) -> "Cursor" :
190+ def execute (self , operation : str , parameters : Optional [Dict [str , str ]] = None ) -> "Cursor" :
185191 """
186192 Execute a query and wait for execution to complete.
187-
193+ Parameters should be given in extended param format style: %(...)<s|d|f>.
194+ For example:
195+ operation = "SELECT * FROM %(table_name)s"
196+ parameters = {"table_name": "my_table_name"}
197+ Will result in the query "SELECT * FROM 'my_table_name' being sent to the server
188198 :returns self
189199 """
190- if query_params is None :
191- sql = operation
192- else :
193- # TODO(https://databricks.atlassian.net/browse/SC-88829) before public release
194- logger .error ("query param substitution currently un-implemented" )
195- sql = operation
200+ if parameters is not None :
201+ operation = operation % self .escaper .escape_args (parameters )
196202
197203 self ._check_not_closed ()
198204 self ._close_and_clear_active_result_set ()
199205 execute_response = self .thrift_backend .execute_command (
200- operation = sql ,
206+ operation = operation ,
201207 session_handle = self .connection ._session_handle ,
202208 max_rows = self .arraysize ,
203209 max_bytes = self .buffer_size_bytes ,
@@ -206,6 +212,19 @@ def execute(self,
206212 self .buffer_size_bytes , self .arraysize )
207213 return self
208214
215+ def executemany (self , operation , seq_of_parameters ):
216+ """
217+ Prepare a database operation (query or command) and then execute it against all parameter
218+ sequences or mappings found in the sequence ``seq_of_parameters``.
219+
220+ Only the final result set is retained.
221+
222+ :returns self
223+ """
224+ for parameters in seq_of_parameters :
225+ self .execute (operation , parameters )
226+ return self
227+
209228 def catalogs (self ) -> "Cursor" :
210229 """
211230 Get all available catalogs.
@@ -327,7 +346,7 @@ def fetchone(self) -> Tuple:
327346 else :
328347 raise Error ("There is no active result set" )
329348
330- def fetchmany (self , n_rows : int ) -> List [Tuple ]:
349+ def fetchmany (self , size : int ) -> List [Tuple ]:
331350 """
332351 Fetch the next set of rows of a query result, returning a sequence of sequences (e.g. a
333352 list of tuples).
@@ -345,7 +364,7 @@ def fetchmany(self, n_rows: int) -> List[Tuple]:
345364 """
346365 self ._check_not_closed ()
347366 if self .active_result_set :
348- return self .active_result_set .fetchmany (n_rows )
367+ return self .active_result_set .fetchmany (size )
349368 else :
350369 raise Error ("There is no active result set" )
351370
@@ -356,10 +375,10 @@ def fetchall_arrow(self):
356375 else :
357376 raise Error ("There is no active result set" )
358377
359- def fetchmany_arrow (self , n_rows ):
378+ def fetchmany_arrow (self , size ):
360379 self ._check_not_closed ()
361380 if self .active_result_set :
362- return self .active_result_set .fetchmany_arrow (n_rows )
381+ return self .active_result_set .fetchmany_arrow (size )
363382 else :
364383 raise Error ("There is no active result set" )
365384
@@ -407,6 +426,24 @@ def description(self) -> Optional[List[Tuple]]:
407426 else :
408427 return None
409428
429+ @property
430+ def rownumber (self ):
431+ """This read-only attribute should provide the current 0-based index of the cursor in the
432+ result set.
433+
434+ The index can be seen as index of the cursor in a sequence (the result set). The next fetch
435+ operation will fetch the row indexed by ``rownumber`` in that sequence.
436+ """
437+ return self .active_result_set .rownumber if self .active_result_set else 0
438+
439+ def setinputsizes (self , sizes ):
440+ """Does nothing by default"""
441+ pass
442+
443+ def setoutputsize (self , size , column = None ):
444+ """Does nothing by default"""
445+ pass
446+
410447
411448class ResultSet :
412449 def __init__ (self ,
@@ -468,16 +505,20 @@ def _convert_arrow_table(self, table):
468505 for row_index in range (n_rows )]
469506 return list_repr
470507
471- def fetchmany_arrow (self , n_rows : int ) -> pyarrow .Table :
508+ @property
509+ def rownumber (self ):
510+ return self ._next_row_index
511+
512+ def fetchmany_arrow (self , size : int ) -> pyarrow .Table :
472513 """
473514 Fetch the next set of rows of a query result, returning a PyArrow table.
474515
475516 An empty sequence is returned when no more rows are available.
476517 """
477- if n_rows < 0 :
478- raise ValueError ("n_rows argument for fetchmany is %s but must be >= 0" , n_rows )
479- results = self .results .next_n_rows (n_rows )
480- n_remaining_rows = n_rows - results .num_rows
518+ if size < 0 :
519+ raise ValueError ("size argument for fetchmany is %s but must be >= 0" , size )
520+ results = self .results .next_n_rows (size )
521+ n_remaining_rows = size - results .num_rows
481522 self ._next_row_index += results .num_rows
482523
483524 while n_remaining_rows > 0 and not self .has_been_closed_server_side and self .has_more_rows :
@@ -519,13 +560,13 @@ def fetchall(self) -> List[Tuple]:
519560 """
520561 return self ._convert_arrow_table (self .fetchall_arrow ())
521562
522- def fetchmany (self , n_rows : int ) -> List [Tuple ]:
563+ def fetchmany (self , size : int ) -> List [Tuple ]:
523564 """
524565 Fetch the next set of rows of a query result, returning a list of lists.
525566
526567 An empty sequence is returned when no more rows are available.
527568 """
528- return self ._convert_arrow_table (self .fetchmany_arrow (n_rows ))
569+ return self ._convert_arrow_table (self .fetchmany_arrow (size ))
529570
530571 def close (self ) -> None :
531572 """
0 commit comments