Skip to content

Commit 85b0ec6

Browse files
feat: add spatial type support (geography, geometry, hierarchyid)
Add SQL_SS_UDT (-151) handling for SQL Server spatial types, enabling geography, geometry, and hierarchyid columns to be fetched as raw bytes. C++ changes (ddbc_bindings.cpp): - SQLGetData_wrap: SQL_SS_UDT falls through to SQL_BINARY - SQLBindColumns: SQL_SS_UDT falls through to SQL_BINARY - FetchBatchData: SQL_SS_UDT falls through to ProcessBinary - calculateRowSize: SQL_SS_UDT with LOB-size fallback for 0/SQL_NO_TOTAL - FetchMany_wrap/FetchAll_wrap: SQL_SS_UDT added to LOB detection Python changes: - constants.py: SQL_SS_UDT = -151 in ConstantsDDBC enum + get_valid_types() - cursor.py: SQL_SS_UDT -> SQL_C_BINARY in _get_c_type_for_sql_type, SQL_SS_UDT -> bytes in _map_data_type Tests: 37 tests covering all three spatial types across fetch paths (fetchone, fetchmany, fetchall, executemany), NULL handling, mixed-column queries, output converters, description metadata, spatial methods, error handling, and binary output consistency.
1 parent c4e647e commit 85b0ec6

File tree

4 files changed

+842
-2
lines changed

4 files changed

+842
-2
lines changed

mssql_python/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ class ConstantsDDBC(Enum):
114114
SQL_FETCH_ABSOLUTE = 5
115115
SQL_FETCH_RELATIVE = 6
116116
SQL_FETCH_BOOKMARK = 8
117+
SQL_SS_UDT = -151
117118
SQL_DATETIMEOFFSET = -155
118119
SQL_SS_TIME2 = -154
119120
SQL_SS_XML = -152
@@ -374,6 +375,7 @@ def get_valid_types(cls) -> set:
374375
ConstantsDDBC.SQL_DATETIMEOFFSET.value,
375376
ConstantsDDBC.SQL_SS_XML.value,
376377
ConstantsDDBC.SQL_GUID.value,
378+
ConstantsDDBC.SQL_SS_UDT.value,
377379
}
378380

379381
# Could also add category methods for convenience

mssql_python/cursor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -865,6 +865,7 @@ def _get_c_type_for_sql_type(self, sql_type: int) -> int:
865865
ddbc_sql_const.SQL_BINARY.value: ddbc_sql_const.SQL_C_BINARY.value,
866866
ddbc_sql_const.SQL_VARBINARY.value: ddbc_sql_const.SQL_C_BINARY.value,
867867
ddbc_sql_const.SQL_LONGVARBINARY.value: ddbc_sql_const.SQL_C_BINARY.value,
868+
ddbc_sql_const.SQL_SS_UDT.value: ddbc_sql_const.SQL_C_BINARY.value,
868869
# ODBC 3.x date/time types (reported by ODBC 18 driver)
869870
ddbc_sql_const.SQL_TYPE_DATE.value: ddbc_sql_const.SQL_C_TYPE_DATE.value,
870871
ddbc_sql_const.SQL_TYPE_TIME.value: ddbc_sql_const.SQL_C_TYPE_TIME.value,
@@ -1097,6 +1098,7 @@ def _map_data_type(self, sql_type):
10971098
ddbc_sql_const.SQL_BINARY.value: bytes,
10981099
ddbc_sql_const.SQL_VARBINARY.value: bytes,
10991100
ddbc_sql_const.SQL_LONGVARBINARY.value: bytes,
1101+
ddbc_sql_const.SQL_SS_UDT.value: bytes,
11001102
# UUID
11011103
ddbc_sql_const.SQL_GUID.value: uuid.UUID,
11021104
# XML — driver reports SQL_SS_XML (-152), fetched as str

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#define MAX_DIGITS_IN_NUMERIC 64
2828
#define SQL_MAX_NUMERIC_LEN 16
2929
#define SQL_SS_XML (-152)
30+
#define SQL_SS_UDT (-151)
3031

3132
#define STRINGIFY_FOR_CASE(x) \
3233
case x: \
@@ -3285,6 +3286,7 @@ SQLRETURN SQLGetData_wrap(SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, p
32853286
}
32863287
break;
32873288
}
3289+
case SQL_SS_UDT:
32883290
case SQL_BINARY:
32893291
case SQL_VARBINARY:
32903292
case SQL_LONGVARBINARY: {
@@ -3555,6 +3557,7 @@ SQLRETURN SQLBindColums(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& column
35553557
ret = SQLBindCol_ptr(hStmt, col, SQL_C_GUID, buffers.guidBuffers[col - 1].data(),
35563558
sizeof(SQLGUID), buffers.indicators[col - 1].data());
35573559
break;
3560+
case SQL_SS_UDT:
35583561
case SQL_BINARY:
35593562
case SQL_VARBINARY:
35603563
case SQL_LONGVARBINARY:
@@ -3683,6 +3686,7 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
36833686
case SQL_WLONGVARCHAR:
36843687
columnProcessors[col] = ColumnProcessors::ProcessWChar;
36853688
break;
3689+
case SQL_SS_UDT:
36863690
case SQL_BINARY:
36873691
case SQL_VARBINARY:
36883692
case SQL_LONGVARBINARY:
@@ -3981,6 +3985,10 @@ size_t calculateRowSize(py::list& columnNames, SQLUSMALLINT numCols) {
39813985
case SQL_BIT:
39823986
rowSize += sizeof(SQLCHAR);
39833987
break;
3988+
case SQL_SS_UDT:
3989+
rowSize += (columnSize == SQL_NO_TOTAL || columnSize == 0)
3990+
? SQL_MAX_LOB_SIZE : columnSize;
3991+
break;
39843992
case SQL_BINARY:
39853993
case SQL_VARBINARY:
39863994
case SQL_LONGVARBINARY:
@@ -4043,7 +4051,8 @@ SQLRETURN FetchMany_wrap(SqlHandlePtr StatementHandle, py::list& rows, int fetch
40434051

40444052
if ((dataType == SQL_WVARCHAR || dataType == SQL_WLONGVARCHAR || dataType == SQL_VARCHAR ||
40454053
dataType == SQL_LONGVARCHAR || dataType == SQL_VARBINARY ||
4046-
dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML) &&
4054+
dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML ||
4055+
dataType == SQL_SS_UDT) &&
40474056
(columnSize == 0 || columnSize == SQL_NO_TOTAL || columnSize > SQL_MAX_LOB_SIZE)) {
40484057
lobColumns.push_back(i + 1); // 1-based
40494058
}
@@ -4177,7 +4186,8 @@ SQLRETURN FetchAll_wrap(SqlHandlePtr StatementHandle, py::list& rows,
41774186

41784187
if ((dataType == SQL_WVARCHAR || dataType == SQL_WLONGVARCHAR || dataType == SQL_VARCHAR ||
41794188
dataType == SQL_LONGVARCHAR || dataType == SQL_VARBINARY ||
4180-
dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML) &&
4189+
dataType == SQL_LONGVARBINARY || dataType == SQL_SS_XML ||
4190+
dataType == SQL_SS_UDT) &&
41814191
(columnSize == 0 || columnSize == SQL_NO_TOTAL || columnSize > SQL_MAX_LOB_SIZE)) {
41824192
lobColumns.push_back(i + 1); // 1-based
41834193
}

0 commit comments

Comments
 (0)