FEAT: Support for Complex Data Type- sql_variant#446
Conversation
There was a problem hiding this comment.
Pull request overview
Adds sql_variant read support to the MSSQL Python driver by introducing the SQL Server-specific type constant and enhancing the C++ fetch path to resolve and deserialize sql_variant values as their underlying base types, with a new test suite to validate behavior across fetch methods.
Changes:
- Added
SQL_SS_VARIANT (-150)to Python constants and allowed it inSQLTypes.get_valid_types(). - Updated the C++ fetch path to detect
sql_variant, map its underlying type, and route decoding through existing type handlers; adjusted LOB detection to force streaming forsql_variant. - Added a dedicated pytest suite covering many base types and fetch patterns for
sql_variant.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
mssql_python/constants.py |
Introduces SQL_SS_VARIANT and includes it in valid SQL types for setinputsizes. |
mssql_python/cursor.py |
Adds a SQL→C type mapping entry for SQL_SS_VARIANT. |
mssql_python/pybind/ddbc_bindings.cpp |
Implements sql_variant type detection/mapping in SQLGetData_wrap and routes sql_variant through the streaming/LOB path for fetchmany/fetchall. |
tests/test_019_sql_variant.py |
New pytest coverage for fetching sql_variant across base types and fetch APIs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
📊 Code Coverage Report
Diff CoverageDiff: main...HEAD, staged and unstaged changes
Summary
mssql_python/pybind/ddbc_bindings.cppLines 2951-2960 2951 return SQL_WVARCHAR;
2952 case SQL_C_DATE:
2953 case SQL_C_TYPE_DATE:
2954 return SQL_TYPE_DATE;
! 2955 case SQL_C_TIME:
! 2956 case SQL_C_TYPE_TIME:
2957 case SQL_SS_VARIANT_TIME:
2958 return SQL_TYPE_TIME;
2959 case SQL_C_TIMESTAMP:
2960 case SQL_C_TYPE_TIMESTAMP:Lines 2964-2976 2964 case SQL_C_GUID:
2965 return SQL_GUID;
2966 case SQL_C_NUMERIC:
2967 return SQL_NUMERIC;
! 2968 case SQL_C_TINYINT:
2969 case SQL_C_UTINYINT:
2970 case SQL_C_STINYINT:
2971 return SQL_TINYINT;
! 2972 default:
2973 // Unknown C type code - fallback to WVARCHAR for string conversion
2974 // Note: SQL Server enforces sql_variant restrictions at INSERT time, preventing
2975 // invalid types (text, ntext, image, timestamp, xml, MAX types, nested variants,
2976 // spatial types, hierarchyid, UDTs) from being stored. By the time we fetch data,Lines 2974-2982 2974 // Note: SQL Server enforces sql_variant restrictions at INSERT time, preventing
2975 // invalid types (text, ntext, image, timestamp, xml, MAX types, nested variants,
2976 // spatial types, hierarchyid, UDTs) from being stored. By the time we fetch data,
2977 // only valid base types exist. This default handles unmapped/future type codes.
! 2978 return SQL_WVARCHAR;
2979 }
2980 }
2981
2982 // Helper function to check if a column requires SQLGetData streaming (LOB or sql_variant)Lines 3037-3049 3037 // Without this probe call, SQLColAttribute returns incorrect type codes.
3038 SQLLEN indicator;
3039 ret = SQLGetData_ptr(hStmt, i, SQL_C_BINARY, NULL, 0, &indicator);
3040 if (!SQL_SUCCEEDED(ret)) {
! 3041 LOG_ERROR("SQLGetData: Failed to probe sql_variant column %d - SQLRETURN=%d", i,
! 3042 ret);
! 3043 row.append(py::none());
! 3044 continue;
! 3045 }
3046 if (indicator == SQL_NULL_DATA) {
3047 row.append(py::none());
3048 continue;
3049 }Lines 3051-3062 3051 SQLLEN variantCType = 0;
3052 ret =
3053 SQLColAttribute_ptr(hStmt, i, SQL_CA_SS_VARIANT_TYPE, NULL, 0, NULL, &variantCType);
3054 if (!SQL_SUCCEEDED(ret)) {
! 3055 LOG_ERROR("SQLGetData: Failed to get sql_variant underlying type for column %d", i);
! 3056 row.append(py::none());
! 3057 continue;
! 3058 }
3059 effectiveDataType = MapVariantCTypeToSQLType(variantCType);
3060 LOG("SQLGetData: sql_variant column %d has variantCType=%ld, mapped to SQL type %d", i,
3061 (long)variantCType, effectiveDataType);
3062 }Lines 4339-4347 4339 ret = SQLFetch_ptr(hStmt);
4340 if (ret == SQL_NO_DATA)
4341 break;
4342 if (!SQL_SUCCEEDED(ret))
! 4343 return ret;
4344
4345 py::list row;
4346 SQLGetData_wrap(StatementHandle, numCols, row, charEncoding,
4347 wcharEncoding); // <-- streams LOBs correctly📋 Files Needing Attention📉 Files with overall lowest coverage (click to expand)mssql_python.pybind.logger_bridge.cpp: 59.2%
mssql_python.pybind.ddbc_bindings.h: 67.8%
mssql_python.pybind.ddbc_bindings.cpp: 70.4%
mssql_python.row.py: 70.5%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.connection.connection.cpp: 75.3%
mssql_python.__init__.py: 77.1%
mssql_python.ddbc_bindings.py: 79.6%
mssql_python.pybind.connection.connection_pool.cpp: 79.6%
mssql_python.connection.py: 85.2%🔗 Quick Links
|
264fa31 to
485c22f
Compare
a9c2429 to
4d7588c
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
2be7cc0 to
7fcc06c
Compare
|
@gargsaumya This is reviewed. Please make sure that we document a performance aspect of using sql variant types on the queries so that customer are aware of the fact that they may see slight degradation. |
- Add SQL_VARIANT constant (-150) to constants.py - Implement preprocessing approach in ddbc_bindings.cpp: * MapVariantCTypeToSQLType helper maps C types to SQL types * SQLGetData_wrap detects sql_variant and maps to base type * Handles old-style date/time C codes (9, 10, 11) * Handles SQL Server TIME type (code 16384) * Routes to existing type conversion logic (no duplication) - Move LOB detection before calculateRowSize in FetchAll_wrap - Add comprehensive test suite (25 tests): * Tests all SQL base types: INT, BIGINT, SMALLINT, TINYINT, REAL, FLOAT, DECIMAL, NUMERIC, BIT, VARCHAR, NVARCHAR, DATE, TIME, DATETIME, DATETIME2, VARBINARY, UNIQUEIDENTIFIER, NULL * Tests all fetch methods: fetchone(), fetchmany(), fetchall() * Tests implicit vs explicit type casting * All tests passing Type mappings: - Integer types → Python int - Float types → Python float - Exact numeric → Python Decimal - Character types → Python str - Date/time types → Python date/time/datetime objects - Binary → Python bytes - GUID → Python str/UUID - NULL → Python None
0e83e85 to
6667135
Compare
Work Item / Issue Reference
Summary
This pull request adds support for the
sql_variantSQL Server type to the Python MSSQL driver, ensuring thatsql_variantcolumns are fetched and mapped correctly to their underlying types. The changes introduce new constants, update type mappings, and enhance the fetch logic to detect and processsql_variantcolumns using their native types, improving compatibility and correctness when handling complex data.Support for sql_variant type
SQL_SS_VARIANTconstant and related attribute constants in bothmssql_python/constants.pyand the C++ binding layer to enable recognition and handling of thesql_varianttype. [1] [2]SQL_SS_VARIANTin the set of valid types for type validation logic.Type mapping and fetch logic
cursor.pyso thatSQL_SS_VARIANTis mapped toSQL_C_BINARY, allowing binary transfer of the variant data.sql_variant's underlying C type to the appropriate SQL data type, enabling the fetch logic to reuse existing code for each possible underlying type.SQLGetData_wrap,FetchMany_wrap, andFetchAll_wrap) to detectsql_variantcolumns, determine their true data types at runtime, and handle them with the correct logic. This includes always using the streaming fetch path forsql_variantcolumns to preserve native type fidelity. [1] [2] [3]Other improvements
sql_variantcolumns are processed. [1] [2] [3] [4]These changes collectively ensure that the driver can now fully support reading
sql_variantcolumns, mapping them to their actual types, and handling them efficiently.