Skip to content

Commit 5077ec5

Browse files
committed
working executemany-uuid
1 parent e6a9bbc commit 5077ec5

File tree

2 files changed

+122
-0
lines changed

2 files changed

+122
-0
lines changed

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1963,6 +1963,82 @@ SQLRETURN BindParameterArray(SQLHANDLE hStmt,
19631963
bufferLength = sizeof(SQL_NUMERIC_STRUCT);
19641964
break;
19651965
}
1966+
// case SQL_C_GUID: {
1967+
// SQLGUID* guidArray = AllocateParamBufferArray<SQLGUID>(tempBuffers, paramSetSize);
1968+
// strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
1969+
1970+
// for (size_t i = 0; i < paramSetSize; ++i) {
1971+
// if (columnValues[i].is_none()) {
1972+
// std::memset(&guidArray[i], 0, sizeof(SQLGUID));
1973+
// strLenOrIndArray[i] = SQL_NULL_DATA;
1974+
// } else {
1975+
// if (!py::isinstance<py::bytes>(columnValues[i])) {
1976+
// ThrowStdException(MakeParamMismatchErrorStr(info.paramCType, paramIndex));
1977+
// }
1978+
// py::bytes uuid_bytes = columnValues[i].cast<py::bytes>();
1979+
// const unsigned char* uuid_data = reinterpret_cast<const unsigned char*>(PyBytes_AS_STRING(uuid_bytes.ptr()));
1980+
// if (PyBytes_GET_SIZE(uuid_bytes.ptr()) != 16) {
1981+
// ThrowStdException("UUID binary data must be exactly 16 bytes long.");
1982+
// }
1983+
1984+
// // Map bytes to SQLGUID fields
1985+
// guidArray[i].Data1 = (static_cast<uint32_t>(uuid_data[3]) << 24) |
1986+
// (static_cast<uint32_t>(uuid_data[2]) << 16) |
1987+
// (static_cast<uint32_t>(uuid_data[1]) << 8) |
1988+
// (static_cast<uint32_t>(uuid_data[0]));
1989+
// guidArray[i].Data2 = (static_cast<uint16_t>(uuid_data[5]) << 8) |
1990+
// (static_cast<uint16_t>(uuid_data[4]));
1991+
// guidArray[i].Data3 = (static_cast<uint16_t>(uuid_data[7]) << 8) |
1992+
// (static_cast<uint16_t>(uuid_data[6]));
1993+
// std::memcpy(guidArray[i].Data4, &uuid_data[8], 8);
1994+
1995+
// strLenOrIndArray[i] = sizeof(SQLGUID);
1996+
// }
1997+
// }
1998+
1999+
// dataPtr = guidArray;
2000+
// bufferLength = sizeof(SQLGUID);
2001+
// break;
2002+
// }
2003+
case SQL_C_GUID: {
2004+
SQLGUID* guidArray = AllocateParamBufferArray<SQLGUID>(tempBuffers, paramSetSize);
2005+
strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
2006+
2007+
py::object uuid_type = py::module_::import("uuid").attr("UUID");
2008+
2009+
for (size_t i = 0; i < paramSetSize; ++i) {
2010+
if (columnValues[i].is_none()) {
2011+
std::memset(&guidArray[i], 0, sizeof(SQLGUID));
2012+
strLenOrIndArray[i] = SQL_NULL_DATA;
2013+
} else if (py::isinstance(columnValues[i], uuid_type)) {
2014+
py::bytes uuid_bytes = columnValues[i].attr("bytes");
2015+
const unsigned char* uuid_data = reinterpret_cast<const unsigned char*>(PyBytes_AS_STRING(uuid_bytes.ptr()));
2016+
2017+
if (PyBytes_GET_SIZE(uuid_bytes.ptr()) != 16) {
2018+
ThrowStdException("UUID binary data must be exactly 16 bytes long.");
2019+
}
2020+
2021+
guidArray[i].Data1 = (static_cast<uint32_t>(uuid_data[3]) << 24) |
2022+
(static_cast<uint32_t>(uuid_data[2]) << 16) |
2023+
(static_cast<uint32_t>(uuid_data[1]) << 8) |
2024+
(static_cast<uint32_t>(uuid_data[0]));
2025+
guidArray[i].Data2 = (static_cast<uint16_t>(uuid_data[5]) << 8) |
2026+
(static_cast<uint16_t>(uuid_data[4]));
2027+
guidArray[i].Data3 = (static_cast<uint16_t>(uuid_data[7]) << 8) |
2028+
(static_cast<uint16_t>(uuid_data[6]));
2029+
std::memcpy(guidArray[i].Data4, &uuid_data[8], 8);
2030+
2031+
strLenOrIndArray[i] = sizeof(SQLGUID);
2032+
} else {
2033+
ThrowStdException(MakeParamMismatchErrorStr(info.paramCType, paramIndex));
2034+
}
2035+
}
2036+
2037+
dataPtr = guidArray;
2038+
bufferLength = sizeof(SQLGUID);
2039+
break;
2040+
}
2041+
19662042
default: {
19672043
ThrowStdException("BindParameterArray: Unsupported C type: " + std::to_string(info.paramCType));
19682044
}

tests/test_004_cursor.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10618,6 +10618,52 @@ def test_decimal_separator_calculations(cursor, db_connection):
1061810618
cursor.execute("DROP TABLE IF EXISTS #pytest_decimal_calc_test")
1061910619
db_connection.commit()
1062010620

10621+
def test_executemany_with_uuids(cursor, db_connection):
10622+
"""Test inserting multiple rows with UUIDs and None using executemany."""
10623+
table_name = "#pytest_uuid_batch"
10624+
try:
10625+
cursor.execute(f"DROP TABLE IF EXISTS {table_name}")
10626+
cursor.execute(f"""
10627+
CREATE TABLE {table_name} (
10628+
id UNIQUEIDENTIFIER,
10629+
description NVARCHAR(50)
10630+
)
10631+
""")
10632+
db_connection.commit()
10633+
10634+
# Prepare test data: mix of UUIDs and None
10635+
test_data = [
10636+
[uuid.uuid4(), "Item 1"],
10637+
[uuid.uuid4(), "Item 2"],
10638+
[None, "Item 3"],
10639+
[uuid.uuid4(), "Item 4"],
10640+
[None, "Item 5"]
10641+
]
10642+
10643+
# Execute batch insert
10644+
cursor.executemany(f"INSERT INTO {table_name} (id, description) VALUES (?, ?)", test_data)
10645+
cursor.connection.commit()
10646+
10647+
# Fetch and verify
10648+
cursor.execute(f"SELECT id, description FROM {table_name}")
10649+
rows = cursor.fetchall()
10650+
10651+
assert len(rows) == len(test_data), "Number of fetched rows does not match inserted rows."
10652+
10653+
for row in rows:
10654+
retrieved_uuid, retrieved_desc = row
10655+
for original_uuid, original_desc in test_data:
10656+
if original_desc == retrieved_desc:
10657+
if original_uuid is None:
10658+
assert retrieved_uuid is None, f"Expected None for '{retrieved_desc}', got {retrieved_uuid}"
10659+
else:
10660+
assert isinstance(retrieved_uuid, uuid.UUID), f"Expected UUID, got {type(retrieved_uuid)}"
10661+
assert retrieved_uuid == original_uuid, f"UUID mismatch for '{retrieved_desc}'"
10662+
break
10663+
finally:
10664+
cursor.execute(f"DROP TABLE IF EXISTS {table_name}")
10665+
db_connection.commit()
10666+
1062110667
def test_close(db_connection):
1062210668
"""Test closing the cursor"""
1062310669
try:

0 commit comments

Comments
 (0)