Skip to content

Commit c28bcb1

Browse files
committed
cleanup
1 parent a1ec891 commit c28bcb1

File tree

2 files changed

+33
-8
lines changed

2 files changed

+33
-8
lines changed

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2948,6 +2948,11 @@ SQLSMALLINT MapVariantCTypeToSQLType(SQLLEN variantCType) {
29482948
case SQL_C_STINYINT:
29492949
return SQL_TINYINT;
29502950
default:
2951+
// Unknown C type code - fallback to WVARCHAR for string conversion
2952+
// Note: SQL Server enforces sql_variant restrictions at INSERT time, preventing
2953+
// invalid types (text, ntext, image, timestamp, xml, MAX types, nested variants,
2954+
// spatial types, hierarchyid, UDTs) from being stored. By the time we fetch data,
2955+
// only valid base types exist. This default handles unmapped/future type codes.
29512956
return SQL_WVARCHAR;
29522957
}
29532958
}
@@ -2990,15 +2995,27 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
29902995
continue;
29912996
}
29922997

2993-
// Preprocess sql_variant: detect underlying type and handle NULL
2998+
// Preprocess sql_variant: detect underlying type to route to correct conversion logic
29942999
SQLSMALLINT effectiveDataType = dataType;
29953000
if (dataType == SQL_SS_VARIANT) {
3001+
// For sql_variant, we MUST call SQLGetData with SQL_C_BINARY (NULL buffer, len=0)
3002+
// first. This serves two purposes:
3003+
// 1. Detects NULL values via the indicator parameter
3004+
// 2. Initializes the variant metadata in the ODBC driver, which is required for
3005+
// SQLColAttribute(SQL_CA_SS_VARIANT_TYPE) to return the correct underlying C type.
3006+
// Without this probe call, SQLColAttribute returns incorrect type codes.
29963007
SQLLEN indicator;
29973008
ret = SQLGetData_ptr(hStmt, i, SQL_C_BINARY, NULL, 0, &indicator);
3009+
if (!SQL_SUCCEEDED(ret)) {
3010+
LOG("SQLGetData: Failed to probe sql_variant column %d - SQLRETURN=%d", i, ret);
3011+
row.append(py::none());
3012+
continue;
3013+
}
29983014
if (indicator == SQL_NULL_DATA) {
29993015
row.append(py::none());
30003016
continue;
30013017
}
3018+
// Now retrieve the underlying C type
30023019
SQLLEN variantCType = 0;
30033020
ret =
30043021
SQLColAttribute_ptr(hStmt, i, SQL_CA_SS_VARIANT_TYPE, NULL, 0, NULL, &variantCType);
@@ -3008,6 +3025,8 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
30083025
continue;
30093026
}
30103027
effectiveDataType = MapVariantCTypeToSQLType(variantCType);
3028+
LOG("SQLGetData: sql_variant column %d has variantCType=%ld, mapped to SQL type %d", i,
3029+
(long)variantCType, effectiveDataType);
30113030
}
30123031

30133032
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)