Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions mssql_python/pybind/ddbc_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2183,6 +2183,33 @@ SQLRETURN BindParameterArray(SQLHANDLE hStmt,
bufferLength = sizeof(SQLGUID);
break;
}
case SQL_C_DEFAULT: {
// Handle NULL parameters - all values in this column should be NULL
LOG("BindParameterArray: Binding SQL_C_DEFAULT (NULL) array - param_index=%d, count=%zu", paramIndex, paramSetSize);

// Verify all values are indeed NULL
for (size_t i = 0; i < paramSetSize; ++i) {
if (!columnValues[i].is_none()) {
LOG("BindParameterArray: SQL_C_DEFAULT non-NULL value detected - param_index=%d, row=%zu", paramIndex, i);
ThrowStdException("SQL_C_DEFAULT (99) should only be used for NULL parameters at index " + std::to_string(paramIndex));
}
}

// For NULL parameters, we need to allocate a minimal buffer and set all indicators to SQL_NULL_DATA
// Use SQL_C_CHAR as a safe default C type for NULL values
char* nullBuffer = AllocateParamBufferArray<char>(tempBuffers, paramSetSize);
strLenOrIndArray = AllocateParamBufferArray<SQLLEN>(tempBuffers, paramSetSize);

for (size_t i = 0; i < paramSetSize; ++i) {
nullBuffer[i] = 0;
strLenOrIndArray[i] = SQL_NULL_DATA;
}

dataPtr = nullBuffer;
bufferLength = 1;
LOG("BindParameterArray: SQL_C_DEFAULT bound - param_index=%d, all_null=true", paramIndex);
break;
}
default: {
LOG("BindParameterArray: Unsupported C type - param_index=%d, C_type=%d", paramIndex, info.paramCType);
ThrowStdException("BindParameterArray: Unsupported C type: " + std::to_string(info.paramCType));
Expand Down
31 changes: 31 additions & 0 deletions tests/test_004_cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1662,6 +1662,37 @@ def test_executemany_empty_parameter_list(cursor, db_connection):
cursor.execute("DROP TABLE IF EXISTS #pytest_empty_params")
db_connection.commit()

def test_executemany_NONE_parameter_list(cursor, db_connection):
"""Test executemany with an NONE parameter list."""
try:
cursor.execute("CREATE TABLE #pytest_empty_params (val VARCHAR(50))")
data = [(None,), (None,)]
cursor.executemany("INSERT INTO #pytest_empty_params VALUES (?)", data)
db_connection.commit()

cursor.execute("SELECT COUNT(*) FROM #pytest_empty_params")
count = cursor.fetchone()[0]
assert count == 2
finally:
cursor.execute("DROP TABLE IF EXISTS #pytest_empty_params")
db_connection.commit()


def test_executemany_MIX_NONE_parameter_list(cursor, db_connection):
"""Test executemany with an NONE parameter list."""
try:
cursor.execute("CREATE TABLE #pytest_empty_params (val VARCHAR(50))")
data = [(None,), ('Test',),(None,)]
cursor.executemany("INSERT INTO #pytest_empty_params VALUES (?)", data)
db_connection.commit()

cursor.execute("SELECT COUNT(*) FROM #pytest_empty_params")
count = cursor.fetchone()[0]
assert count == 3
finally:
cursor.execute("DROP TABLE IF EXISTS #pytest_empty_params")
db_connection.commit()


def test_executemany_Decimal_list(cursor, db_connection):
"""Test executemany with an decimal parameter list."""
Expand Down
Loading