@@ -135,6 +135,10 @@ def __init__(self, connection: "Connection", timeout: int = 0) -> None:
135135
136136 self ._cached_column_map = None
137137 self ._cached_converter_map = None
138+ self ._uuid_str_indices = None # Pre-computed UUID column indices for str conversion
139+ # Cache the effective native_uuid setting for this cursor's connection.
140+ # Resolution order: connection._native_uuid (if not None) → module-level setting.
141+ self ._conn_native_uuid = getattr (self .connection , "_native_uuid" , None )
138142 self ._next_row_index = 0 # internal: index of the next row the driver will return (0-based)
139143 self ._has_result_set = False # Track if we have an active result set
140144 self ._skip_increment_for_next_fetch = (
@@ -1009,6 +1013,32 @@ def _build_converter_map(self):
10091013
10101014 return converter_map
10111015
1016+ def _compute_uuid_str_indices (self ):
1017+ """
1018+ Compute the tuple of column indices whose uuid.UUID values should be
1019+ stringified (as uppercase), based on the effective native_uuid setting.
1020+
1021+ Resolution order: connection-level (if set) → module-level (fallback).
1022+
1023+ Returns:
1024+ tuple of int or None: Column indices to stringify, or None when
1025+ native_uuid is True — meaning zero per-row overhead.
1026+ """
1027+ if not self .description :
1028+ return None
1029+
1030+ effective_native_uuid = (
1031+ self ._conn_native_uuid
1032+ if self ._conn_native_uuid is not None
1033+ else get_settings ().native_uuid
1034+ )
1035+ if not effective_native_uuid :
1036+ indices = tuple (
1037+ i for i , desc in enumerate (self .description ) if desc and desc [1 ] is uuid .UUID
1038+ )
1039+ return indices if indices else None
1040+ return None
1041+
10121042 def _get_column_and_converter_maps (self ):
10131043 """
10141044 Get column map and converter map for Row construction (thread-safe).
@@ -1429,20 +1459,13 @@ def execute( # pylint: disable=too-many-locals,too-many-branches,too-many-state
14291459 col_desc [0 ]: i for i , col_desc in enumerate (self .description )
14301460 }
14311461 self ._cached_converter_map = self ._build_converter_map ()
1462+ self ._uuid_str_indices = self ._compute_uuid_str_indices ()
14321463 else :
14331464 self .rowcount = ddbc_bindings .DDBCSQLRowCount (self .hstmt )
14341465 self ._clear_rownumber ()
14351466 self ._cached_column_map = None
14361467 self ._cached_converter_map = None
1437-
1438- # After successful execution, initialize description if there are results
1439- column_metadata = []
1440- try :
1441- ddbc_bindings .DDBCSQLDescribeCol (self .hstmt , column_metadata )
1442- self ._initialize_description (column_metadata )
1443- except Exception as e :
1444- # If describe fails, it's likely there are no results (e.g., for INSERT)
1445- self .description = None
1468+ self ._uuid_str_indices = None
14461469
14471470 self ._reset_inputsizes () # Reset input sizes after execution
14481471 # Return self for method chaining
@@ -2283,14 +2306,29 @@ def executemany( # pylint: disable=too-many-locals,too-many-branches,too-many-s
22832306 check_error (ddbc_sql_const .SQL_HANDLE_STMT .value , self .hstmt , ret )
22842307 self .rowcount = ddbc_bindings .DDBCSQLRowCount (self .hstmt )
22852308 self .last_executed_stmt = operation
2286- self ._initialize_description ()
2309+
2310+ # Fetch column metadata (e.g. for INSERT … OUTPUT)
2311+ column_metadata = []
2312+ try :
2313+ ddbc_bindings .DDBCSQLDescribeCol (self .hstmt , column_metadata )
2314+ self ._initialize_description (column_metadata )
2315+ except Exception : # pylint: disable=broad-exception-caught
2316+ self .description = None
22872317
22882318 if self .description :
22892319 self .rowcount = - 1
22902320 self ._reset_rownumber ()
2321+ self ._cached_column_map = {
2322+ col_desc [0 ]: i for i , col_desc in enumerate (self .description )
2323+ }
2324+ self ._cached_converter_map = self ._build_converter_map ()
2325+ self ._uuid_str_indices = self ._compute_uuid_str_indices ()
22912326 else :
22922327 self .rowcount = ddbc_bindings .DDBCSQLRowCount (self .hstmt )
22932328 self ._clear_rownumber ()
2329+ self ._cached_column_map = None
2330+ self ._cached_converter_map = None
2331+ self ._uuid_str_indices = None
22942332 finally :
22952333 # Reset input sizes after execution
22962334 self ._reset_inputsizes ()
@@ -2338,7 +2376,13 @@ def fetchone(self) -> Union[None, Row]:
23382376
23392377 # Get column and converter maps
23402378 column_map , converter_map = self ._get_column_and_converter_maps ()
2341- return Row (row_data , column_map , cursor = self , converter_map = converter_map )
2379+ return Row (
2380+ row_data ,
2381+ column_map ,
2382+ cursor = self ,
2383+ converter_map = converter_map ,
2384+ uuid_str_indices = self ._uuid_str_indices ,
2385+ )
23422386 except Exception as e :
23432387 # On error, don't increment rownumber - rethrow the error
23442388 raise e
@@ -2396,8 +2440,15 @@ def fetchmany(self, size: Optional[int] = None) -> List[Row]:
23962440 column_map , converter_map = self ._get_column_and_converter_maps ()
23972441
23982442 # Convert raw data to Row objects
2443+ uuid_idx = self ._uuid_str_indices
23992444 return [
2400- Row (row_data , column_map , cursor = self , converter_map = converter_map )
2445+ Row (
2446+ row_data ,
2447+ column_map ,
2448+ cursor = self ,
2449+ converter_map = converter_map ,
2450+ uuid_str_indices = uuid_idx ,
2451+ )
24012452 for row_data in rows_data
24022453 ]
24032454 except Exception as e :
@@ -2449,8 +2500,15 @@ def fetchall(self) -> List[Row]:
24492500 column_map , converter_map = self ._get_column_and_converter_maps ()
24502501
24512502 # Convert raw data to Row objects
2503+ uuid_idx = self ._uuid_str_indices
24522504 return [
2453- Row (row_data , column_map , cursor = self , converter_map = converter_map )
2505+ Row (
2506+ row_data ,
2507+ column_map ,
2508+ cursor = self ,
2509+ converter_map = converter_map ,
2510+ uuid_str_indices = uuid_idx ,
2511+ )
24542512 for row_data in rows_data
24552513 ]
24562514 except Exception as e :
@@ -2476,6 +2534,7 @@ def nextset(self) -> Union[bool, None]:
24762534 # Clear cached column and converter maps for the new result set
24772535 self ._cached_column_map = None
24782536 self ._cached_converter_map = None
2537+ self ._uuid_str_indices = None
24792538
24802539 # Skip to the next result set
24812540 ret = ddbc_bindings .DDBCSQLMoreResults (self .hstmt )
@@ -2501,6 +2560,7 @@ def nextset(self) -> Union[bool, None]:
25012560 col_desc [0 ]: i for i , col_desc in enumerate (self .description )
25022561 }
25032562 self ._cached_converter_map = self ._build_converter_map ()
2563+ self ._uuid_str_indices = self ._compute_uuid_str_indices ()
25042564 except Exception as e : # pylint: disable=broad-exception-caught
25052565 # If describe fails, there might be no results in this result set
25062566 self .description = None
0 commit comments