@@ -1711,87 +1711,220 @@ SQLRETURN SQLFetch_wrap(SqlHandlePtr StatementHandle) {
17111711 return SQLFetch_ptr (StatementHandle->get ());
17121712}
17131713
1714+ // static py::object FetchLobColumnData(SQLHSTMT hStmt,
1715+ // SQLUSMALLINT colIndex,
1716+ // SQLSMALLINT cType,
1717+ // bool isWideChar,
1718+ // bool isBinary)
1719+ // {
1720+ // std::vector<char> buffer;
1721+ // SQLLEN indicator = 0;
1722+ // SQLRETURN ret;
1723+ // int loopCount = 0;
1724+
1725+ // while (true) {
1726+ // ++loopCount;
1727+ // std::vector<char> chunk(DAE_CHUNK_SIZE);
1728+ // ret = SQLGetData_ptr(
1729+ // hStmt,
1730+ // colIndex,
1731+ // cType,
1732+ // chunk.data(),
1733+ // DAE_CHUNK_SIZE,
1734+ // &indicator
1735+ // );
1736+ // if (indicator == SQL_NULL_DATA) {
1737+ // LOG("Loop {}: Column {} is NULL", loopCount, colIndex);
1738+ // return py::none();
1739+ // }
1740+ // if (!SQL_SUCCEEDED(ret) && ret != SQL_SUCCESS_WITH_INFO) {
1741+ // LOG("Loop {}: Error fetching col={} with cType={} ret={}", loopCount, colIndex, cType, ret);
1742+ // ThrowStdException("Error fetching column data");
1743+ // }
1744+ // // SQLLEN copyCount = 0;
1745+ // SQLLEN copyCount = DAE_CHUNK_SIZE;
1746+ // if (indicator >= 0 && indicator != SQL_NO_TOTAL) {
1747+ // copyCount = std::min<SQLLEN>(indicator - buffer.size(), DAE_CHUNK_SIZE);
1748+ // }
1749+ // // else {
1750+ // // copyCount = DAE_CHUNK_SIZE;
1751+ // // }
1752+ // if (isWideChar && (copyCount % sizeof(SQLWCHAR) != 0)) {
1753+ // LOG("Loop {}: Warning – copyCount {} not multiple of {}", loopCount, copyCount, sizeof(SQLWCHAR));
1754+ // copyCount -= copyCount % sizeof(SQLWCHAR);
1755+ // }
1756+
1757+
1758+ // // Check if last byte(s) is a null terminator
1759+ // if (copyCount > 0) {
1760+ // if (!isWideChar && chunk[copyCount - 1] == '\0') {
1761+ // --copyCount;
1762+ // LOG("Loop {}: Trimmed null terminator (narrow)", loopCount);
1763+ // } else if (copyCount >= sizeof(SQLWCHAR)) {
1764+ // auto wcharBuf = reinterpret_cast<const SQLWCHAR*>(chunk.data());
1765+ // if (wcharBuf[(copyCount / sizeof(SQLWCHAR)) - 1] == L'\0') {
1766+ // copyCount -= sizeof(SQLWCHAR);
1767+ // LOG("Loop {}: Trimmed null terminator (wide)", loopCount);
1768+ // }
1769+ // }
1770+ // }
1771+ // if (copyCount > 0) {
1772+ // buffer.insert(buffer.end(), chunk.begin(), chunk.begin() + copyCount);
1773+ // LOG("Loop {}: Appended {} bytes", loopCount, copyCount);
1774+ // }
1775+ // if (ret == SQL_SUCCESS) {
1776+ // LOG("Loop {}: SQL_SUCCESS → no more data", loopCount);
1777+ // break;
1778+ // }
1779+ // }
1780+ // LOG("FetchLobColumnData: Total bytes collected = {}", buffer.size());
1781+
1782+ // if (indicator == 0 || buffer.empty()) {
1783+ // LOG("FetchLobColumnData: Returning empty string for col {}", colIndex);
1784+ // return py::str("");
1785+ // }
1786+
1787+ // if (isWideChar) {
1788+ // // std::wstring wstr(reinterpret_cast<const wchar_t*>(buffer.data()),
1789+ // // buffer.size() / sizeof(wchar_t));
1790+ // // LOG("FetchLobColumnData: Returning wide string of length {}", wstr.length());
1791+ // // return py::cast(wstr);
1792+ // std::wstring wstr = SQLWCHARToWString(reinterpret_cast<const SQLWCHAR*>(buffer.data()), buffer.size() / sizeof(SQLWCHAR));
1793+ // LOG("FetchLobColumnData: Returning wide string of length {}", wstr.length());
1794+ // return py::cast(wstr);
1795+ // // }
1796+ // if (isWideChar) {
1797+ // if (buffer.size() % sizeof(SQLWCHAR) != 0) {
1798+ // LOG("FetchLobColumnData: Buffer size {} not aligned with {}", buffer.size(), sizeof(SQLWCHAR));
1799+ // throw std::runtime_error("Invalid wide char buffer size");
1800+ // }
1801+ // #ifdef _WIN32
1802+ // std::wstring wstr(reinterpret_cast<const wchar_t*>(buffer.data()), buffer.size() / sizeof(wchar_t));
1803+ // #else
1804+ // size_t length = buffer.size() / sizeof(SQLWCHAR);
1805+ // std::wstring wstr = SQLWCHARToWString(reinterpret_cast<const SQLWCHAR*>(buffer.data()), length);
1806+ // #endif
1807+ // LOG("FetchLobColumnData: Returning wide string of length {}", wstr.length());
1808+ // return py::cast(wstr);
1809+ // }
1810+ // if (isBinary) {
1811+
1812+ // LOG("FetchLobColumnData: Returning binary of {} bytes", buffer.size());
1813+ // return py::bytes(buffer.data(), buffer.size());
1814+ // }
1815+ // std::string str(buffer.data(), buffer.size());
1816+ // LOG("FetchLobColumnData: Returning narrow string of length {}", str.length());
1817+ // return py::str(str);
1818+ // }
1819+
17141820static py::object FetchLobColumnData (SQLHSTMT hStmt,
17151821 SQLUSMALLINT colIndex,
17161822 SQLSMALLINT cType,
17171823 bool isWideChar,
17181824 bool isBinary)
17191825{
17201826 std::vector<char > buffer;
1721- SQLLEN indicator = 0 ;
1722- SQLRETURN ret;
1827+ SQLRETURN ret = SQL_SUCCESS_WITH_INFO;
17231828 int loopCount = 0 ;
17241829
17251830 while (true ) {
17261831 ++loopCount;
1727- std::vector<char > chunk (DAE_CHUNK_SIZE);
1728- ret = SQLGetData_ptr (
1729- hStmt,
1730- colIndex,
1731- cType,
1732- chunk.data (),
1733- DAE_CHUNK_SIZE,
1734- &indicator
1735- );
1832+
1833+ std::vector<char > chunk (DAE_CHUNK_SIZE, 0 ); // Fill with zeros to handle padding safely
1834+ SQLLEN indicator = 0 ;
1835+
1836+ ret = SQLGetData_ptr (hStmt,
1837+ colIndex,
1838+ cType,
1839+ chunk.data (),
1840+ DAE_CHUNK_SIZE,
1841+ &indicator);
1842+
17361843 if (indicator == SQL_NULL_DATA) {
17371844 LOG (" Loop {}: Column {} is NULL" , loopCount, colIndex);
17381845 return py::none ();
17391846 }
17401847 if (!SQL_SUCCEEDED (ret) && ret != SQL_SUCCESS_WITH_INFO) {
1741- LOG (" Loop {}: Error fetching col= {} with cType={} ret={}" , loopCount, colIndex, cType, ret);
1742- return py::none ( );
1848+ LOG (" Loop {}: Error fetching column {} with cType={} ret={}" , loopCount, colIndex, cType, ret);
1849+ ThrowStdException ( " Error fetching column data " );
17431850 }
1744- SQLLEN copyCount = 0 ;
1851+
1852+ size_t bytesRead = 0 ;
1853+
1854+ // Determine how many bytes to process
17451855 if (indicator > 0 && indicator != SQL_NO_TOTAL) {
1746- copyCount = std::min<SQLLEN>( indicator, DAE_CHUNK_SIZE);
1856+ bytesRead = std::min<size_t >( static_cast < size_t >( indicator) , DAE_CHUNK_SIZE);
17471857 } else {
1748- copyCount = DAE_CHUNK_SIZE;
1858+ // If unknown, assume full buffer minus possible null terminator padding
1859+ bytesRead = DAE_CHUNK_SIZE;
17491860 }
17501861
1751- // Check if last byte(s) is a null terminator
1752- if (copyCount > 0 ) {
1753- if (!isWideChar && chunk[copyCount - 1 ] == ' \0 ' ) {
1754- --copyCount;
1755- LOG (" Loop {}: Trimmed null terminator (narrow)" , loopCount);
1756- } else if (copyCount >= sizeof (wchar_t )) {
1757- auto wcharBuf = reinterpret_cast <const wchar_t *>(chunk.data ());
1758- if (wcharBuf[(copyCount / sizeof (wchar_t )) - 1 ] == L' \0 ' ) {
1759- copyCount -= sizeof (wchar_t );
1760- LOG (" Loop {}: Trimmed null terminator (wide)" , loopCount);
1862+ // For character data, trim trailing null terminators
1863+ if (!isBinary && bytesRead > 0 ) {
1864+ if (!isWideChar) {
1865+ while (bytesRead > 0 && chunk[bytesRead - 1 ] == ' \0 ' ) {
1866+ --bytesRead;
1867+ }
1868+ if (bytesRead < DAE_CHUNK_SIZE) {
1869+ LOG (" Loop {}: Trimmed null terminator (narrow)" , loopCount);
1870+ }
1871+ } else {
1872+ // Wide characters
1873+ size_t wcharSize = sizeof (SQLWCHAR);
1874+ if (bytesRead >= wcharSize) {
1875+ auto wcharBuf = reinterpret_cast <const SQLWCHAR*>(chunk.data ());
1876+ size_t wcharCount = bytesRead / wcharSize;
1877+ while (wcharCount > 0 && wcharBuf[wcharCount - 1 ] == 0 ) {
1878+ --wcharCount;
1879+ bytesRead -= wcharSize;
1880+ }
1881+ if (bytesRead < DAE_CHUNK_SIZE) {
1882+ LOG (" Loop {}: Trimmed null terminator (wide)" , loopCount);
1883+ }
17611884 }
17621885 }
17631886 }
1764- if (copyCount > 0 ) {
1765- buffer.insert (buffer.end (), chunk.begin (), chunk.begin () + copyCount);
1766- LOG (" Loop {}: Appended {} bytes" , loopCount, copyCount);
1887+
1888+ if (bytesRead > 0 ) {
1889+ buffer.insert (buffer.end (), chunk.begin (), chunk.begin () + bytesRead);
1890+ LOG (" Loop {}: Appended {} bytes" , loopCount, bytesRead);
17671891 }
1892+
17681893 if (ret == SQL_SUCCESS) {
17691894 LOG (" Loop {}: SQL_SUCCESS → no more data" , loopCount);
17701895 break ;
17711896 }
17721897 }
1898+
17731899 LOG (" FetchLobColumnData: Total bytes collected = {}" , buffer.size ());
17741900
1775- if (indicator == 0 || buffer.empty ()) {
1776- LOG (" FetchLobColumnData: Returning empty string for col {}" , colIndex);
1901+ // If buffer is empty, return empty string or bytes
1902+ if (buffer.empty ()) {
1903+ if (isBinary) {
1904+ return py::bytes (" " );
1905+ }
17771906 return py::str (" " );
17781907 }
17791908
1909+ // Convert the collected buffer to appropriate Python type
17801910 if (isWideChar) {
1781- std::wstring wstr (reinterpret_cast <const wchar_t *>(buffer.data ()),
1782- buffer.size () / sizeof (wchar_t ));
1911+ std::wstring wstr = SQLWCHARToWString (reinterpret_cast <const SQLWCHAR*>(buffer.data ()), buffer.size () / sizeof (SQLWCHAR));
17831912 LOG (" FetchLobColumnData: Returning wide string of length {}" , wstr.length ());
17841913 return py::cast (wstr);
17851914 }
1915+
17861916 if (isBinary) {
17871917 LOG (" FetchLobColumnData: Returning binary of {} bytes" , buffer.size ());
17881918 return py::bytes (buffer.data (), buffer.size ());
17891919 }
1920+
1921+ // Default: narrow string
17901922 std::string str (buffer.data (), buffer.size ());
17911923 LOG (" FetchLobColumnData: Returning narrow string of length {}" , str.length ());
17921924 return py::str (str);
17931925}
17941926
1927+
17951928// Helper function to retrieve column data
17961929SQLRETURN SQLGetData_wrap (SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, py::list& row) {
17971930 LOG (" Get data from columns" );
0 commit comments