@@ -158,6 +158,14 @@ def __init__(self, connection: "Connection", timeout: int = 0) -> None:
158158 self ._conn_native_uuid = getattr (self .connection , "_native_uuid" , None )
159159 self ._next_row_index = 0 # internal: index of the next row the driver will return (0-based)
160160 self ._has_result_set = False # Track if we have an active result set
161+ # Cache decoding encoding strings — these don't change between fetches,
162+ # so we avoid 2 method calls + 2 dict.get() per fetch call.
163+ self ._cached_char_encoding = self ._get_decoding_settings (
164+ ddbc_sql_const .SQL_CHAR .value
165+ ).get ("encoding" , "utf-8" )
166+ self ._cached_wchar_encoding = self ._get_decoding_settings (
167+ ddbc_sql_const .SQL_WCHAR .value
168+ ).get ("encoding" , "utf-16le" )
161169 self ._skip_increment_for_next_fetch = (
162170 False # Track if we need to skip incrementing the row index
163171 )
@@ -173,11 +181,7 @@ def _is_unicode_string(self, param: str) -> bool:
173181 Returns:
174182 True if the string contains non-ASCII characters, False otherwise.
175183 """
176- try :
177- param .encode ("ascii" )
178- return False # Can be encoded to ASCII, so not Unicode
179- except UnicodeEncodeError :
180- return True # Contains non-ASCII characters, so treat as Unicode
184+ return not param .isascii ()
181185
182186 def _parse_date (self , param : str ) -> Optional [datetime .date ]:
183187 """
@@ -895,45 +899,51 @@ def _reset_inputsizes(self) -> None:
895899 """Reset input sizes after execution"""
896900 self ._inputsizes = None
897901
902+ # Pre-built constant lookup table — avoids rebuilding ~30 entries on every call.
903+ # Used by setinputsizes fallback path (PR #549 fast path doesn't need this).
904+ _SQL_TO_C_TYPE = None
905+
906+ @classmethod
907+ def _get_sql_to_c_type_map (cls ):
908+ if cls ._SQL_TO_C_TYPE is None :
909+ cls ._SQL_TO_C_TYPE = {
910+ ddbc_sql_const .SQL_CHAR .value : ddbc_sql_const .SQL_C_CHAR .value ,
911+ ddbc_sql_const .SQL_VARCHAR .value : ddbc_sql_const .SQL_C_CHAR .value ,
912+ ddbc_sql_const .SQL_LONGVARCHAR .value : ddbc_sql_const .SQL_C_CHAR .value ,
913+ ddbc_sql_const .SQL_WCHAR .value : ddbc_sql_const .SQL_C_WCHAR .value ,
914+ ddbc_sql_const .SQL_WVARCHAR .value : ddbc_sql_const .SQL_C_WCHAR .value ,
915+ ddbc_sql_const .SQL_WLONGVARCHAR .value : ddbc_sql_const .SQL_C_WCHAR .value ,
916+ ddbc_sql_const .SQL_DECIMAL .value : ddbc_sql_const .SQL_C_NUMERIC .value ,
917+ ddbc_sql_const .SQL_NUMERIC .value : ddbc_sql_const .SQL_C_NUMERIC .value ,
918+ ddbc_sql_const .SQL_BIT .value : ddbc_sql_const .SQL_C_BIT .value ,
919+ ddbc_sql_const .SQL_TINYINT .value : ddbc_sql_const .SQL_C_TINYINT .value ,
920+ ddbc_sql_const .SQL_SMALLINT .value : ddbc_sql_const .SQL_C_SHORT .value ,
921+ ddbc_sql_const .SQL_INTEGER .value : ddbc_sql_const .SQL_C_LONG .value ,
922+ ddbc_sql_const .SQL_BIGINT .value : ddbc_sql_const .SQL_C_SBIGINT .value ,
923+ ddbc_sql_const .SQL_REAL .value : ddbc_sql_const .SQL_C_FLOAT .value ,
924+ ddbc_sql_const .SQL_FLOAT .value : ddbc_sql_const .SQL_C_DOUBLE .value ,
925+ ddbc_sql_const .SQL_DOUBLE .value : ddbc_sql_const .SQL_C_DOUBLE .value ,
926+ ddbc_sql_const .SQL_BINARY .value : ddbc_sql_const .SQL_C_BINARY .value ,
927+ ddbc_sql_const .SQL_VARBINARY .value : ddbc_sql_const .SQL_C_BINARY .value ,
928+ ddbc_sql_const .SQL_LONGVARBINARY .value : ddbc_sql_const .SQL_C_BINARY .value ,
929+ ddbc_sql_const .SQL_SS_UDT .value : ddbc_sql_const .SQL_C_BINARY .value ,
930+ ddbc_sql_const .SQL_TYPE_DATE .value : ddbc_sql_const .SQL_C_TYPE_DATE .value ,
931+ ddbc_sql_const .SQL_TYPE_TIME .value : ddbc_sql_const .SQL_C_TYPE_TIME .value ,
932+ ddbc_sql_const .SQL_TYPE_TIMESTAMP .value : ddbc_sql_const .SQL_C_TYPE_TIMESTAMP .value ,
933+ ddbc_sql_const .SQL_SS_TIME2 .value : ddbc_sql_const .SQL_C_TYPE_TIME .value ,
934+ ddbc_sql_const .SQL_DATETIMEOFFSET .value : ddbc_sql_const .SQL_C_SS_TIMESTAMPOFFSET .value ,
935+ ddbc_sql_const .SQL_DATE .value : ddbc_sql_const .SQL_C_TYPE_DATE .value ,
936+ ddbc_sql_const .SQL_TIME .value : ddbc_sql_const .SQL_C_TYPE_TIME .value ,
937+ ddbc_sql_const .SQL_TIMESTAMP .value : ddbc_sql_const .SQL_C_TYPE_TIMESTAMP .value ,
938+ ddbc_sql_const .SQL_GUID .value : ddbc_sql_const .SQL_C_GUID .value ,
939+ ddbc_sql_const .SQL_SS_XML .value : ddbc_sql_const .SQL_C_WCHAR .value ,
940+ ddbc_sql_const .SQL_SS_VARIANT .value : ddbc_sql_const .SQL_C_BINARY .value ,
941+ }
942+ return cls ._SQL_TO_C_TYPE
943+
898944 def _get_c_type_for_sql_type (self , sql_type : int ) -> int :
899945 """Map SQL type to appropriate C type for parameter binding."""
900- sql_to_c_type = {
901- ddbc_sql_const .SQL_CHAR .value : ddbc_sql_const .SQL_C_CHAR .value ,
902- ddbc_sql_const .SQL_VARCHAR .value : ddbc_sql_const .SQL_C_CHAR .value ,
903- ddbc_sql_const .SQL_LONGVARCHAR .value : ddbc_sql_const .SQL_C_CHAR .value ,
904- ddbc_sql_const .SQL_WCHAR .value : ddbc_sql_const .SQL_C_WCHAR .value ,
905- ddbc_sql_const .SQL_WVARCHAR .value : ddbc_sql_const .SQL_C_WCHAR .value ,
906- ddbc_sql_const .SQL_WLONGVARCHAR .value : ddbc_sql_const .SQL_C_WCHAR .value ,
907- ddbc_sql_const .SQL_DECIMAL .value : ddbc_sql_const .SQL_C_NUMERIC .value ,
908- ddbc_sql_const .SQL_NUMERIC .value : ddbc_sql_const .SQL_C_NUMERIC .value ,
909- ddbc_sql_const .SQL_BIT .value : ddbc_sql_const .SQL_C_BIT .value ,
910- ddbc_sql_const .SQL_TINYINT .value : ddbc_sql_const .SQL_C_TINYINT .value ,
911- ddbc_sql_const .SQL_SMALLINT .value : ddbc_sql_const .SQL_C_SHORT .value ,
912- ddbc_sql_const .SQL_INTEGER .value : ddbc_sql_const .SQL_C_LONG .value ,
913- ddbc_sql_const .SQL_BIGINT .value : ddbc_sql_const .SQL_C_SBIGINT .value ,
914- ddbc_sql_const .SQL_REAL .value : ddbc_sql_const .SQL_C_FLOAT .value ,
915- ddbc_sql_const .SQL_FLOAT .value : ddbc_sql_const .SQL_C_DOUBLE .value ,
916- ddbc_sql_const .SQL_DOUBLE .value : ddbc_sql_const .SQL_C_DOUBLE .value ,
917- ddbc_sql_const .SQL_BINARY .value : ddbc_sql_const .SQL_C_BINARY .value ,
918- ddbc_sql_const .SQL_VARBINARY .value : ddbc_sql_const .SQL_C_BINARY .value ,
919- ddbc_sql_const .SQL_LONGVARBINARY .value : ddbc_sql_const .SQL_C_BINARY .value ,
920- ddbc_sql_const .SQL_SS_UDT .value : ddbc_sql_const .SQL_C_BINARY .value ,
921- # ODBC 3.x date/time types (reported by ODBC 18 driver)
922- ddbc_sql_const .SQL_TYPE_DATE .value : ddbc_sql_const .SQL_C_TYPE_DATE .value ,
923- ddbc_sql_const .SQL_TYPE_TIME .value : ddbc_sql_const .SQL_C_TYPE_TIME .value ,
924- ddbc_sql_const .SQL_TYPE_TIMESTAMP .value : ddbc_sql_const .SQL_C_TYPE_TIMESTAMP .value ,
925- ddbc_sql_const .SQL_SS_TIME2 .value : ddbc_sql_const .SQL_C_TYPE_TIME .value ,
926- ddbc_sql_const .SQL_DATETIMEOFFSET .value : ddbc_sql_const .SQL_C_SS_TIMESTAMPOFFSET .value ,
927- # ODBC 2.x aliases (accepted by setinputsizes via SQLTypes)
928- ddbc_sql_const .SQL_DATE .value : ddbc_sql_const .SQL_C_TYPE_DATE .value ,
929- ddbc_sql_const .SQL_TIME .value : ddbc_sql_const .SQL_C_TYPE_TIME .value ,
930- ddbc_sql_const .SQL_TIMESTAMP .value : ddbc_sql_const .SQL_C_TYPE_TIMESTAMP .value ,
931- # Other types
932- ddbc_sql_const .SQL_GUID .value : ddbc_sql_const .SQL_C_GUID .value ,
933- ddbc_sql_const .SQL_SS_XML .value : ddbc_sql_const .SQL_C_WCHAR .value ,
934- ddbc_sql_const .SQL_SS_VARIANT .value : ddbc_sql_const .SQL_C_BINARY .value ,
935- }
936- return sql_to_c_type .get (sql_type , ddbc_sql_const .SQL_C_DEFAULT .value )
946+ return self ._get_sql_to_c_type_map ().get (sql_type , ddbc_sql_const .SQL_C_DEFAULT .value )
937947
938948 def _create_parameter_types_list ( # pylint: disable=too-many-arguments,too-many-positional-arguments
939949 self ,
@@ -2453,26 +2463,27 @@ def fetchone(self) -> Union[None, Row]:
24532463 """
24542464 self ._check_closed () # Check if the cursor is closed
24552465
2456- char_decoding = self ._get_decoding_settings (ddbc_sql_const .SQL_CHAR .value )
2457- wchar_decoding = self ._get_decoding_settings (ddbc_sql_const .SQL_WCHAR .value )
2466+ # Use cached encoding strings — eliminates 2 method calls + 2 dict.get() per fetch
2467+ char_enc = self ._cached_char_encoding
2468+ wchar_enc = self ._cached_wchar_encoding
24582469
24592470 # Fetch raw data
24602471 row_data = []
24612472 try :
24622473 ret = ddbc_bindings .DDBCSQLFetchOne (
24632474 self .hstmt ,
24642475 row_data ,
2465- char_decoding . get ( "encoding" , "utf-8" ) ,
2466- wchar_decoding . get ( "encoding" , "utf-16le" ) ,
2476+ char_enc ,
2477+ wchar_enc ,
24672478 )
24682479
2469- if self .hstmt :
2480+ # Only retrieve diag records on SQL_SUCCESS_WITH_INFO.
2481+ if ret == ddbc_sql_const .SQL_SUCCESS_WITH_INFO .value and self .hstmt :
24702482 self .messages .extend (ddbc_bindings .DDBCSQLGetAllDiagRecords (self .hstmt ))
24712483
24722484 if ret == ddbc_sql_const .SQL_NO_DATA .value :
24732485 # No more data available
24742486 if self ._next_row_index == 0 and self .description is not None :
2475- # This is an empty result set, set rowcount to 0
24762487 self .rowcount = 0
24772488 return None
24782489
@@ -2487,6 +2498,9 @@ def fetchone(self) -> Union[None, Row]:
24872498
24882499 # Get column and converter maps
24892500 column_map , converter_map = self ._get_column_and_converter_maps ()
2501+ # Fast path: skip __init__ overhead when no converters/UUID processing
2502+ if not converter_map and not self ._uuid_str_indices :
2503+ return Row ._fast_create (row_data , column_map , self )
24902504 return Row (
24912505 row_data ,
24922506 column_map ,
@@ -2518,8 +2532,9 @@ def fetchmany(self, size: Optional[int] = None) -> List[Row]:
25182532 if size <= 0 :
25192533 return []
25202534
2521- char_decoding = self ._get_decoding_settings (ddbc_sql_const .SQL_CHAR .value )
2522- wchar_decoding = self ._get_decoding_settings (ddbc_sql_const .SQL_WCHAR .value )
2535+ # Use cached encoding strings
2536+ char_enc = self ._cached_char_encoding
2537+ wchar_enc = self ._cached_wchar_encoding
25232538
25242539 # Fetch raw data
25252540 rows_data = []
@@ -2528,11 +2543,11 @@ def fetchmany(self, size: Optional[int] = None) -> List[Row]:
25282543 self .hstmt ,
25292544 rows_data ,
25302545 size ,
2531- char_decoding . get ( "encoding" , "utf-8" ) ,
2532- wchar_decoding . get ( "encoding" , "utf-16le" ) ,
2546+ char_enc ,
2547+ wchar_enc ,
25332548 )
25342549
2535- if self .hstmt :
2550+ if ret == ddbc_sql_const . SQL_SUCCESS_WITH_INFO . value and self .hstmt :
25362551 self .messages .extend (ddbc_bindings .DDBCSQLGetAllDiagRecords (self .hstmt ))
25372552
25382553 # Update rownumber for the number of rows actually fetched
@@ -2552,6 +2567,11 @@ def fetchmany(self, size: Optional[int] = None) -> List[Row]:
25522567
25532568 # Convert raw data to Row objects
25542569 uuid_idx = self ._uuid_str_indices
2570+ # Fast path: build Row objects in C++ — avoids Python loop overhead
2571+ if not converter_map and not uuid_idx :
2572+ return ddbc_bindings .construct_rows (
2573+ rows_data , Row , column_map , self
2574+ )
25552575 return [
25562576 Row (
25572577 row_data ,
@@ -2577,23 +2597,24 @@ def fetchall(self) -> List[Row]:
25772597 if not self ._has_result_set and self .description :
25782598 self ._reset_rownumber ()
25792599
2580- char_decoding = self ._get_decoding_settings (ddbc_sql_const .SQL_CHAR .value )
2581- wchar_decoding = self ._get_decoding_settings (ddbc_sql_const .SQL_WCHAR .value )
2600+ # Use cached encoding strings
2601+ char_enc = self ._cached_char_encoding
2602+ wchar_enc = self ._cached_wchar_encoding
25822603
25832604 # Fetch raw data
25842605 rows_data = []
25852606 try :
25862607 ret = ddbc_bindings .DDBCSQLFetchAll (
25872608 self .hstmt ,
25882609 rows_data ,
2589- char_decoding . get ( "encoding" , "utf-8" ) ,
2590- wchar_decoding . get ( "encoding" , "utf-16le" ) ,
2610+ char_enc ,
2611+ wchar_enc ,
25912612 )
25922613
25932614 # Check for errors
25942615 check_error (ddbc_sql_const .SQL_HANDLE_STMT .value , self .hstmt , ret )
25952616
2596- if self .hstmt :
2617+ if ret == ddbc_sql_const . SQL_SUCCESS_WITH_INFO . value and self .hstmt :
25972618 self .messages .extend (ddbc_bindings .DDBCSQLGetAllDiagRecords (self .hstmt ))
25982619
25992620 # Update rownumber for the number of rows actually fetched
@@ -2612,6 +2633,11 @@ def fetchall(self) -> List[Row]:
26122633
26132634 # Convert raw data to Row objects
26142635 uuid_idx = self ._uuid_str_indices
2636+ # Fast path: build Row objects in C++ — avoids Python loop overhead
2637+ if not converter_map and not uuid_idx :
2638+ return ddbc_bindings .construct_rows (
2639+ rows_data , Row , column_map , self
2640+ )
26152641 return [
26162642 Row (
26172643 row_data ,
0 commit comments