Skip to content

Commit 728f622

Browse files
committed
cleanup
1 parent 258a3e1 commit 728f622

2 files changed

Lines changed: 33 additions & 8 deletions

File tree

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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) {

tests/test_019_sql_variant.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,16 @@ def variant_test_table(cursor, db_connection):
4949
table_name = "#pytest_sql_variant"
5050
drop_table_if_exists(cursor, table_name)
5151

52-
cursor.execute(f"""
52+
cursor.execute(
53+
f"""
5354
CREATE TABLE {table_name} (
5455
id INT PRIMARY KEY,
5556
variant_col SQL_VARIANT,
5657
base_type NVARCHAR(50), -- What SQL type is stored in variant
5758
description NVARCHAR(100)
5859
)
59-
""")
60+
"""
61+
)
6062
db_connection.commit()
6163

6264
# Insert test data with explicit CAST for each SQL base type
@@ -102,10 +104,12 @@ def variant_test_table(cursor, db_connection):
102104
]
103105

104106
for row in test_data:
105-
cursor.execute(f"""
107+
cursor.execute(
108+
f"""
106109
INSERT INTO {table_name} (id, variant_col, base_type, description)
107110
VALUES ({row[0]}, {row[1]}, '{row[2]}', '{row[3]}')
108-
""")
111+
"""
112+
)
109113

110114
# Also test implicit type conversion (what SQL Server chooses)
111115
cursor.execute(f"INSERT INTO {table_name} VALUES (20, 123, 'int', 'Implicit int literal')")
@@ -205,7 +209,7 @@ def test_sql_variant_float(cursor, variant_test_table):
205209

206210

207211
def test_sql_variant_decimal(cursor, variant_test_table):
208-
"""Test sql_vari with DECIMAL base type returns Python Decimal"""
212+
"""Test sql_variant with DECIMAL base type returns Python Decimal"""
209213
cursor.execute(f"SELECT id, variant_col, base_type FROM {variant_test_table} WHERE id = 7")
210214
row = cursor.fetchone()
211215

@@ -496,12 +500,14 @@ def test_sql_variant_large_dataset(cursor, db_connection):
496500
table_name = "#pytest_sql_variant_large"
497501
drop_table_if_exists(cursor, table_name)
498502

499-
cursor.execute(f"""
503+
cursor.execute(
504+
f"""
500505
CREATE TABLE {table_name} (
501506
id INT PRIMARY KEY,
502507
variant_col SQL_VARIANT
503508
)
504-
""")
509+
"""
510+
)
505511
db_connection.commit()
506512

507513
# Insert 100 rows with explicit CAST for each type

0 commit comments

Comments
 (0)