@@ -844,7 +844,11 @@ def _reset_inputsizes(self) -> None:
844844 self ._inputsizes = None
845845
846846 def _get_c_type_for_sql_type (self , sql_type : int ) -> int :
847- """Map SQL type to appropriate C type for parameter binding"""
847+ """Map SQL type to appropriate C type for parameter binding.
848+
849+ Includes SQL_SS_UDT(-151) for CLR UDTs (geography, geometry, hierarchyid),
850+ mapped to SQL_C_BINARY.
851+ """
848852 sql_to_c_type = {
849853 ddbc_sql_const .SQL_CHAR .value : ddbc_sql_const .SQL_C_CHAR .value ,
850854 ddbc_sql_const .SQL_VARCHAR .value : ddbc_sql_const .SQL_C_CHAR .value ,
@@ -943,9 +947,11 @@ def _initialize_description(self, column_metadata: Optional[Any] = None) -> None
943947 """Initialize the description attribute from column metadata."""
944948 if not column_metadata :
945949 self .description = None
950+ self ._column_sql_types = None
946951 return
947952
948953 description = []
954+ sql_types = []
949955 for _ , col in enumerate (column_metadata ):
950956 # Get column name - lowercase it if the lowercase flag is set
951957 column_name = col ["ColumnName" ]
@@ -954,6 +960,8 @@ def _initialize_description(self, column_metadata: Optional[Any] = None) -> None
954960 if get_settings ().lowercase :
955961 column_name = column_name .lower ()
956962
963+ sql_types .append (col ["DataType" ])
964+
957965 # Add to description tuple (7 elements as per PEP-249)
958966 description .append (
959967 (
@@ -967,6 +975,7 @@ def _initialize_description(self, column_metadata: Optional[Any] = None) -> None
967975 )
968976 )
969977 self .description = description
978+ self ._column_sql_types = sql_types
970979
971980 def _build_converter_map (self ):
972981 """
@@ -982,14 +991,24 @@ def _build_converter_map(self):
982991 return None
983992
984993 converter_map = []
994+ raw_types = getattr (self , "_column_sql_types" , None )
985995
986- for desc in self .description :
996+ for i , desc in enumerate ( self .description ) :
987997 if desc is None :
988998 converter_map .append (None )
989999 continue
990- sql_type = desc [1 ]
991- converter = self .connection .get_output_converter (sql_type )
992- # If no converter found for the SQL type, try the WVARCHAR converter as a fallback
1000+
1001+ converter = None
1002+
1003+ # First try lookup by raw SQL type code (e.g. -151 for SQL_SS_UDT)
1004+ if raw_types and i < len (raw_types ):
1005+ converter = self .connection .get_output_converter (raw_types [i ])
1006+
1007+ # Fall back to lookup by Python type from description
1008+ if converter is None :
1009+ converter = self .connection .get_output_converter (desc [1 ])
1010+
1011+ # Last resort: try the WVARCHAR converter as a catch-all fallback
9931012 if converter is None :
9941013 from mssql_python .constants import ConstantsDDBC
9951014
@@ -1027,8 +1046,17 @@ def _map_data_type(self, sql_type):
10271046 """
10281047 Map SQL data type to Python data type.
10291048
1049+ Maps the ODBC SQL type code returned by SQLDescribeCol to the
1050+ corresponding Python type for cursor.description[i][1].
1051+
1052+ SQL Server-specific types (from msodbcsql.h):
1053+ SQL_SS_UDT(-151) for CLR UDTs (geography, geometry, hierarchyid)
1054+ SQL_SS_XML(-152) for xml columns
1055+ SQL_SS_TIME2(-154) for time columns
1056+ SQL_DATETIMEOFFSET(-155) for datetimeoffset columns
1057+
10301058 Args:
1031- sql_type: SQL data type.
1059+ sql_type: SQL data type code from SQLDescribeCol .
10321060
10331061 Returns:
10341062 Corresponding Python data type.
0 commit comments