@@ -2963,6 +2963,11 @@ SQLSMALLINT MapVariantCTypeToSQLType(SQLLEN variantCType) {
29632963 case SQL_C_STINYINT:
29642964 return SQL_TINYINT;
29652965 default :
2966+ // Unknown C type code - fallback to WVARCHAR for string conversion
2967+ // Note: SQL Server enforces sql_variant restrictions at INSERT time, preventing
2968+ // invalid types (text, ntext, image, timestamp, xml, MAX types, nested variants,
2969+ // spatial types, hierarchyid, UDTs) from being stored. By the time we fetch data,
2970+ // only valid base types exist. This default handles unmapped/future type codes.
29662971 return SQL_WVARCHAR;
29672972 }
29682973}
@@ -3005,15 +3010,27 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
30053010 continue ;
30063011 }
30073012
3008- // Preprocess sql_variant: detect underlying type and handle NULL
3013+ // Preprocess sql_variant: detect underlying type to route to correct conversion logic
30093014 SQLSMALLINT effectiveDataType = dataType;
30103015 if (dataType == SQL_SS_VARIANT) {
3016+ // For sql_variant, we MUST call SQLGetData with SQL_C_BINARY (NULL buffer, len=0)
3017+ // first. This serves two purposes:
3018+ // 1. Detects NULL values via the indicator parameter
3019+ // 2. Initializes the variant metadata in the ODBC driver, which is required for
3020+ // SQLColAttribute(SQL_CA_SS_VARIANT_TYPE) to return the correct underlying C type.
3021+ // Without this probe call, SQLColAttribute returns incorrect type codes.
30113022 SQLLEN indicator;
30123023 ret = SQLGetData_ptr (hStmt, i, SQL_C_BINARY, NULL , 0 , &indicator);
3024+ if (!SQL_SUCCEEDED (ret)) {
3025+ LOG (" SQLGetData: Failed to probe sql_variant column %d - SQLRETURN=%d" , i, ret);
3026+ row.append (py::none ());
3027+ continue ;
3028+ }
30133029 if (indicator == SQL_NULL_DATA) {
30143030 row.append (py::none ());
30153031 continue ;
30163032 }
3033+ // Now retrieve the underlying C type
30173034 SQLLEN variantCType = 0 ;
30183035 ret =
30193036 SQLColAttribute_ptr (hStmt, i, SQL_CA_SS_VARIANT_TYPE, NULL , 0 , NULL , &variantCType);
@@ -3023,6 +3040,8 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
30233040 continue ;
30243041 }
30253042 effectiveDataType = MapVariantCTypeToSQLType (variantCType);
3043+ LOG (" SQLGetData: sql_variant column %d has variantCType=%ld, mapped to SQL type %d" , i,
3044+ (long )variantCType, effectiveDataType);
30263045 }
30273046
30283047 switch (effectiveDataType) {
0 commit comments