From 878c7bbed5b2d718aea0687f6fcddce0a74cf079 Mon Sep 17 00:00:00 2001 From: Subrata Paitandi Date: Mon, 17 Nov 2025 20:10:34 +0530 Subject: [PATCH 1/4] mssql_python/cursor.py --- mssql_python/cursor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mssql_python/cursor.py b/mssql_python/cursor.py index 1026507e..f655ea01 100644 --- a/mssql_python/cursor.py +++ b/mssql_python/cursor.py @@ -307,7 +307,7 @@ def _map_sql_type( # pylint: disable=too-many-arguments,too-many-positional-arg logger.debug('_map_sql_type: NULL parameter - index=%d', i) return ( ddbc_sql_const.SQL_VARCHAR.value, - ddbc_sql_const.SQL_C_DEFAULT.value, + ddbc_sql_const.SQL_C_CHAR.value, 1, 0, False, From 87fd98c4f4340feb7a677c80a7971f7a7df73e06 Mon Sep 17 00:00:00 2001 From: Subrata Paitandi Date: Mon, 17 Nov 2025 20:33:42 +0530 Subject: [PATCH 2/4] test case addition --- tests/test_004_cursor.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_004_cursor.py b/tests/test_004_cursor.py index c7d4d5bb..7abfdd2a 100644 --- a/tests/test_004_cursor.py +++ b/tests/test_004_cursor.py @@ -1662,6 +1662,21 @@ 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_Decimal_list(cursor, db_connection): """Test executemany with an decimal parameter list.""" From ce3a27daa2efc2c83c60af14897b5fb8d8070cd4 Mon Sep 17 00:00:00 2001 From: Subrata Paitandi Date: Mon, 17 Nov 2025 20:44:54 +0530 Subject: [PATCH 3/4] reverting python changes and fixing it in ddbc --- mssql_python/cursor.py | 2 +- mssql_python/pybind/ddbc_bindings.cpp | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/mssql_python/cursor.py b/mssql_python/cursor.py index f655ea01..1026507e 100644 --- a/mssql_python/cursor.py +++ b/mssql_python/cursor.py @@ -307,7 +307,7 @@ def _map_sql_type( # pylint: disable=too-many-arguments,too-many-positional-arg logger.debug('_map_sql_type: NULL parameter - index=%d', i) return ( ddbc_sql_const.SQL_VARCHAR.value, - ddbc_sql_const.SQL_C_CHAR.value, + ddbc_sql_const.SQL_C_DEFAULT.value, 1, 0, False, diff --git a/mssql_python/pybind/ddbc_bindings.cpp b/mssql_python/pybind/ddbc_bindings.cpp index 28d17b71..41cfba59 100644 --- a/mssql_python/pybind/ddbc_bindings.cpp +++ b/mssql_python/pybind/ddbc_bindings.cpp @@ -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(tempBuffers, paramSetSize); + strLenOrIndArray = AllocateParamBufferArray(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)); From 7c5a0dd5e84ea4cd5efdcf440b358767d75abf4f Mon Sep 17 00:00:00 2001 From: Subrata Paitandi Date: Mon, 17 Nov 2025 22:21:19 +0530 Subject: [PATCH 4/4] mix data set with NONE --- tests/test_004_cursor.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_004_cursor.py b/tests/test_004_cursor.py index 7abfdd2a..7012ba4e 100644 --- a/tests/test_004_cursor.py +++ b/tests/test_004_cursor.py @@ -1678,6 +1678,22 @@ def test_executemany_NONE_parameter_list(cursor, db_connection): 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.""" try: