Skip to content

Commit 0927dda

Browse files
feat: add spatial type support (geography, geometry, hierarchyid)
Add SQL_SS_UDT (-151) handling so geography, geometry, and hierarchyid columns are fetched as bytes instead of raising errors. C++ changes (ddbc_bindings.cpp): - Define SQL_SS_UDT constant - Add case fallthroughs to SQL_BINARY in SQLGetData_wrap, SQLBindColums, and FetchBatchData - Separate calculateRowSize case with LOB-size fallback - Include SQL_SS_UDT in LOB detection for FetchMany_wrap and FetchAll_wrap Python changes: - constants.py: SQL_SS_UDT = -151 in ConstantsDDBC enum - cursor.py: SQL_SS_UDT -> SQL_C_BINARY in sql_to_c_type map - cursor.py: SQL_SS_UDT -> bytes in sql_to_python_type map Tests: - 37 tests covering all three UDT types (geography, geometry, hierarchyid) - Fetch paths: fetchone, fetchmany, fetchall, executemany - NULL handling, mixed columns, binary round-trips, error cases - Output converter and cursor.description metadata verification
1 parent d424c6f commit 0927dda

4 files changed

Lines changed: 1102 additions & 2 deletions

File tree

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_C_SS_TIMESTAMPOFFSET = 0x4001
119120
SQL_SCOPE_CURROW = 0
@@ -363,6 +364,7 @@ def get_valid_types(cls) -> set:
363364
ConstantsDDBC.SQL_TIME.value,
364365
ConstantsDDBC.SQL_TIMESTAMP.value,
365366
ConstantsDDBC.SQL_GUID.value,
367+
ConstantsDDBC.SQL_SS_UDT.value,
366368
}
367369

368370
# 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
ddbc_sql_const.SQL_DATE.value: ddbc_sql_const.SQL_C_TYPE_DATE.value,
869870
ddbc_sql_const.SQL_TIME.value: ddbc_sql_const.SQL_C_TYPE_TIME.value,
870871
ddbc_sql_const.SQL_TIMESTAMP.value: ddbc_sql_const.SQL_C_TYPE_TIMESTAMP.value,
@@ -1052,6 +1053,7 @@ def _map_data_type(self, sql_type):
10521053
ddbc_sql_const.SQL_BINARY.value: bytes,
10531054
ddbc_sql_const.SQL_VARBINARY.value: bytes,
10541055
ddbc_sql_const.SQL_LONGVARBINARY.value: bytes,
1056+
ddbc_sql_const.SQL_SS_UDT.value: bytes,
10551057
ddbc_sql_const.SQL_GUID.value: uuid.UUID,
10561058
# Add more mappings as needed
10571059
}

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)