@@ -581,6 +581,29 @@ void EnsureNativeFetchBufferSize(std::vector<ElementType>& buffer, size_t requir
581581 ResizeNativeFetchBuffer (buffer, newSize, reservedBytes);
582582}
583583
584+ size_t ExtendNativeFetchBuffer (std::vector<char >& buffer, size_t currentSize, size_t appendSize,
585+ size_t & reservedBytes) {
586+ const size_t requiredSize =
587+ CheckedAddSize (currentSize, appendSize, " Native fetch buffer size is too large" );
588+ EnsureNativeFetchBufferSize (buffer, requiredSize, reservedBytes);
589+ return requiredSize;
590+ }
591+
592+ py::tuple TestLobBufferGrowth (size_t chunkCount, size_t chunkSize) {
593+ std::vector<char > buffer;
594+ size_t dataSize = 0 ;
595+ size_t reservedBytes = 0 ;
596+ size_t growthCount = 0 ;
597+ for (size_t chunk = 0 ; chunk < chunkCount; ++chunk) {
598+ const size_t previousAllocation = buffer.size ();
599+ dataSize = ExtendNativeFetchBuffer (buffer, dataSize, chunkSize, reservedBytes);
600+ if (buffer.size () != previousAllocation) {
601+ ++growthCount;
602+ }
603+ }
604+ return py::make_tuple (growthCount, dataSize, buffer.size (), reservedBytes);
605+ }
606+
584607template <typename ElementType>
585608std::unique_ptr<ElementType[]> AllocateArrowArray (size_t count, size_t & reservedBytes,
586609 const char * errorMessage) {
@@ -2484,6 +2507,7 @@ SQLRETURN SQLExecute_wrap(const SqlHandlePtr statementHandle,
24842507 bool use_prepare,
24852508 const py::dict& encoding_settings) {
24862509 PERF_TIMER (" SQLExecute_wrap" );
2510+ ValidateInputSizes (input_sizes.ptr ());
24872511 if (!statementHandle || !statementHandle->get ()) {
24882512 return SQL_INVALID_HANDLE ;
24892513 }
@@ -3306,9 +3330,14 @@ SQLRETURN BindParameterArray(SqlHandle& handle, SQLHANDLE hStmt, const py::list&
33063330
33073331SQLRETURN SQLExecuteMany_wrap (const SqlHandlePtr statementHandle, const std::u16string& query,
33083332 const py::list& columnwise_params,
3309- std::vector<ParamInfo>& paramInfos, size_t paramSetSize ,
3333+ std::vector<ParamInfo>& paramInfos, py::handle paramSetSizeArg ,
33103334 const py::dict& encodingSettings) {
33113335 PERF_TIMER (" SQLExecuteMany_wrap" );
3336+ const size_t paramSetSize = static_cast <size_t >(
3337+ ValidateNativeRowCountArgument (paramSetSizeArg, " Parameter set size" , false ));
3338+ if (!statementHandle || !statementHandle->get ()) {
3339+ return SQL_INVALID_HANDLE ;
3340+ }
33123341 statementHandle->resultMetadata .clear ();
33133342 LOG (" SQLExecuteMany: Starting batch execution - param_count=%zu, "
33143343 " param_set_size=%zu" ,
@@ -3646,6 +3675,7 @@ static py::object FetchLobColumnDataImpl(
36463675 SQLGetDataFunc getData, bool (*hasTruncationDiagnostic)(SQLHSTMT )) {
36473676 PERF_TIMER (" FetchLobColumnData" );
36483677 std::vector<char > buffer;
3678+ size_t dataSize = 0 ;
36493679 size_t reservedBytes = 0 ;
36503680 ReserveNativeFetchBytes (reservedBytes, DAE_CHUNK_SIZE , sizeof (char ));
36513681 SQLRETURN ret = SQL_SUCCESS_WITH_INFO ;
@@ -3742,10 +3772,8 @@ static py::object FetchLobColumnDataImpl(
37423772 ThrowStdException (" LOB fetch truncation made no progress" );
37433773 }
37443774 if (bytesRead > 0 ) {
3745- const size_t previousSize = buffer.size ();
3746- const size_t requiredSize =
3747- CheckedAddSize (previousSize, bytesRead, " LOB fetch buffer is too large" );
3748- ResizeNativeFetchBuffer (buffer, requiredSize, reservedBytes);
3775+ const size_t previousSize = dataSize;
3776+ dataSize = ExtendNativeFetchBuffer (buffer, dataSize, bytesRead, reservedBytes);
37493777 std::copy_n (chunk.data (), bytesRead, buffer.data () + previousSize);
37503778 LOG (" FetchLobColumnData: Appended %zu bytes at loop %d" , bytesRead, loopCount);
37513779 }
@@ -3755,21 +3783,21 @@ static py::object FetchLobColumnDataImpl(
37553783 break ;
37563784 }
37573785 }
3758- LOG (" FetchLobColumnData: Total bytes collected=%zu for column %d" , buffer. size () , colIndex);
3786+ LOG (" FetchLobColumnData: Total bytes collected=%zu for column %d" , dataSize , colIndex);
37593787
3760- if (buffer. empty () ) {
3788+ if (dataSize == 0 ) {
37613789 if (isBinary) {
37623790 return py::bytes (" " );
37633791 }
37643792 return py::str (" " );
37653793 }
37663794 if (isWideChar) {
3767- if (buffer. size () % sizeof (SQLWCHAR ) != 0 ) {
3795+ if (dataSize % sizeof (SQLWCHAR ) != 0 ) {
37683796 ThrowStdException (" Wide-character LOB data has an invalid byte length" );
37693797 }
37703798 int byteOrder = -1 ;
37713799 PyObject* decoded =
3772- PyUnicode_DecodeUTF16 (buffer.data (), static_cast <Py_ssize_t>(buffer. size () ),
3800+ PyUnicode_DecodeUTF16 (buffer.data (), static_cast <Py_ssize_t>(dataSize ),
37733801 " strict" , &byteOrder);
37743802 if (decoded == nullptr ) {
37753803 throw py::error_already_set ();
@@ -3779,18 +3807,18 @@ static py::object FetchLobColumnDataImpl(
37793807 if (isBinary) {
37803808 LOG (" FetchLobColumnData: Returning binary data - %zu bytes for column "
37813809 " %d" ,
3782- buffer. size () , colIndex);
3783- return py::bytes (buffer.data (), buffer. size () );
3810+ dataSize , colIndex);
3811+ return py::bytes (buffer.data (), dataSize );
37843812 }
37853813
37863814 // For SQL_C_CHAR data, decode using the appropriate encoding.
37873815 const std::string effectiveCharEncoding = GetEffectiveCharDecoding (charEncoding);
3788- py::bytes raw_bytes (buffer.data (), buffer. size () );
3816+ py::bytes raw_bytes (buffer.data (), dataSize );
37893817 try {
37903818 py::object decoded = raw_bytes.attr (" decode" )(effectiveCharEncoding, " strict" );
37913819 LOG (" FetchLobColumnData: Decoded narrow string with '%s' - %zu bytes -> %zu chars for "
37923820 " column %d" ,
3793- effectiveCharEncoding.c_str (), buffer. size () , py::len (decoded), colIndex);
3821+ effectiveCharEncoding.c_str (), dataSize , py::len (decoded), colIndex);
37943822 return decoded;
37953823 } catch (const py::error_already_set& e) {
37963824 LOG_ERROR (" FetchLobColumnData: Failed to decode with '%s' for column %d: %s" ,
@@ -7538,6 +7566,8 @@ PYBIND11_MODULE(ddbc_bindings, m) {
75387566 " Fetch an arrow batch of given length from the result set" , py::arg (" StatementHandle" ),
75397567 py::arg (" capsules" ), py::arg (" arrowBatchSize" ), py::arg (" charCtype" ),
75407568 py::arg (" messages" ) = py::none ());
7569+ m.def (" _test_lob_buffer_growth" , &TestLobBufferGrowth, py::arg (" chunk_count" ),
7570+ py::arg (" chunk_size" ));
75417571 m.def (" DDBCSQLFreeHandle" , &SQLFreeHandle_wrap, " Free a handle" );
75427572 m.def (" DDBCSQLResetStmt" , &SQLResetStmt_wrap,
75437573 " Close cursor and unbind params without freeing HSTMT" );
0 commit comments