Skip to content

Commit 510bf91

Browse files
committed
working executemany-uuid
1 parent 621b1cc commit 510bf91

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
@@ -1908,6 +1908,82 @@ SQLRETURN BindParameterArray(SQLHANDLE hStmt,
19081908
bufferLength = sizeof(SQL_NUMERIC_STRUCT);
19091909
break;
19101910
}
1911+
// case SQL_C_GUID: {
1912+
// SQLGUID* guidArray = AllocateParamBufferArray<SQLGUID>(tempBuffers, paramSetSize);
1913+
// strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
1914+
1915+
// for (size_t i = 0; i < paramSetSize; ++i) {
1916+
// if (columnValues[i].is_none()) {
1917+
// std::memset(&guidArray[i], 0, sizeof(SQLGUID));
1918+
// strLenOrIndArray[i] = SQL_NULL_DATA;
1919+
// } else {
1920+
// if (!py::isinstance<py::bytes>(columnValues[i])) {
1921+
// ThrowStdException(MakeParamMismatchErrorStr(info.paramCType, paramIndex));
1922+
// }
1923+
// py::bytes uuid_bytes = columnValues[i].cast<py::bytes>();
1924+
// const unsigned char* uuid_data = reinterpret_cast<const unsigned char*>(PyBytes_AS_STRING(uuid_bytes.ptr()));
1925+
// if (PyBytes_GET_SIZE(uuid_bytes.ptr()) != 16) {
1926+
// ThrowStdException("UUID binary data must be exactly 16 bytes long.");
1927+
// }
1928+
1929+
// // Map bytes to SQLGUID fields
1930+
// guidArray[i].Data1 = (static_cast<uint32_t>(uuid_data[3]) << 24) |
1931+
// (static_cast<uint32_t>(uuid_data[2]) << 16) |
1932+
// (static_cast<uint32_t>(uuid_data[1]) << 8) |
1933+
// (static_cast<uint32_t>(uuid_data[0]));
1934+
// guidArray[i].Data2 = (static_cast<uint16_t>(uuid_data[5]) << 8) |
1935+
// (static_cast<uint16_t>(uuid_data[4]));
1936+
// guidArray[i].Data3 = (static_cast<uint16_t>(uuid_data[7]) << 8) |
1937+
// (static_cast<uint16_t>(uuid_data[6]));
1938+
// std::memcpy(guidArray[i].Data4, &uuid_data[8], 8);
1939+
1940+
// strLenOrIndArray[i] = sizeof(SQLGUID);
1941+
// }
1942+
// }
1943+
1944+
// dataPtr = guidArray;
1945+
// bufferLength = sizeof(SQLGUID);
1946+
// break;
1947+
// }
1948+
case SQL_C_GUID: {
1949+
SQLGUID* guidArray = AllocateParamBufferArray<SQLGUID>(tempBuffers, paramSetSize);
1950+
strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);
1951+
1952+
py::object uuid_type = py::module_::import("uuid").attr("UUID");
1953+
1954+
for (size_t i = 0; i < paramSetSize; ++i) {
1955+
if (columnValues[i].is_none()) {
1956+
std::memset(&guidArray[i], 0, sizeof(SQLGUID));
1957+
strLenOrIndArray[i] = SQL_NULL_DATA;
1958+
} else if (py::isinstance(columnValues[i], uuid_type)) {
1959+
py::bytes uuid_bytes = columnValues[i].attr("bytes");
1960+
const unsigned char* uuid_data = reinterpret_cast<const unsigned char*>(PyBytes_AS_STRING(uuid_bytes.ptr()));
1961+
1962+
if (PyBytes_GET_SIZE(uuid_bytes.ptr()) != 16) {
1963+
ThrowStdException("UUID binary data must be exactly 16 bytes long.");
1964+
}
1965+
1966+
guidArray[i].Data1 = (static_cast<uint32_t>(uuid_data[3]) << 24) |
1967+
(static_cast<uint32_t>(uuid_data[2]) << 16) |
1968+
(static_cast<uint32_t>(uuid_data[1]) << 8) |
1969+
(static_cast<uint32_t>(uuid_data[0]));
1970+
guidArray[i].Data2 = (static_cast<uint16_t>(uuid_data[5]) << 8) |
1971+
(static_cast<uint16_t>(uuid_data[4]));
1972+
guidArray[i].Data3 = (static_cast<uint16_t>(uuid_data[7]) << 8) |
1973+
(static_cast<uint16_t>(uuid_data[6]));
1974+
std::memcpy(guidArray[i].Data4, &uuid_data[8], 8);
1975+
1976+
strLenOrIndArray[i] = sizeof(SQLGUID);
1977+
} else {
1978+
ThrowStdException(MakeParamMismatchErrorStr(info.paramCType, paramIndex));
1979+
}
1980+
}
1981+
1982+
dataPtr = guidArray;
1983+
bufferLength = sizeof(SQLGUID);
1984+
break;
1985+
}
1986+
19111987
default: {
19121988
ThrowStdException("BindParameterArray: Unsupported C type: " + std::to_string(info.paramCType));
19131989
}

tests/test_004_cursor.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10292,6 +10292,52 @@ def test_uuid_insert_with_none(cursor, db_connection):
1029210292
cursor.execute(f"DROP TABLE IF EXISTS {table_name}")
1029310293
db_connection.commit()
1029410294

10295+
def test_executemany_with_uuids(cursor, db_connection):
10296+
"""Test inserting multiple rows with UUIDs and None using executemany."""
10297+
table_name = "#pytest_uuid_batch"
10298+
try:
10299+
cursor.execute(f"DROP TABLE IF EXISTS {table_name}")
10300+
cursor.execute(f"""
10301+
CREATE TABLE {table_name} (
10302+
id UNIQUEIDENTIFIER,
10303+
description NVARCHAR(50)
10304+
)
10305+
""")
10306+
db_connection.commit()
10307+
10308+
# Prepare test data: mix of UUIDs and None
10309+
test_data = [
10310+
[uuid.uuid4(), "Item 1"],
10311+
[uuid.uuid4(), "Item 2"],
10312+
[None, "Item 3"],
10313+
[uuid.uuid4(), "Item 4"],
10314+
[None, "Item 5"]
10315+
]
10316+
10317+
# Execute batch insert
10318+
cursor.executemany(f"INSERT INTO {table_name} (id, description) VALUES (?, ?)", test_data)
10319+
cursor.connection.commit()
10320+
10321+
# Fetch and verify
10322+
cursor.execute(f"SELECT id, description FROM {table_name}")
10323+
rows = cursor.fetchall()
10324+
10325+
assert len(rows) == len(test_data), "Number of fetched rows does not match inserted rows."
10326+
10327+
for row in rows:
10328+
retrieved_uuid, retrieved_desc = row
10329+
for original_uuid, original_desc in test_data:
10330+
if original_desc == retrieved_desc:
10331+
if original_uuid is None:
10332+
assert retrieved_uuid is None, f"Expected None for '{retrieved_desc}', got {retrieved_uuid}"
10333+
else:
10334+
assert isinstance(retrieved_uuid, uuid.UUID), f"Expected UUID, got {type(retrieved_uuid)}"
10335+
assert retrieved_uuid == original_uuid, f"UUID mismatch for '{retrieved_desc}'"
10336+
break
10337+
finally:
10338+
cursor.execute(f"DROP TABLE IF EXISTS {table_name}")
10339+
db_connection.commit()
10340+
1029510341
def test_close(db_connection):
1029610342
"""Test closing the cursor"""
1029710343
try:

0 commit comments

Comments
 (0)