@@ -1711,112 +1711,6 @@ 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-
18201714static py::object FetchLobColumnData (SQLHSTMT hStmt,
18211715 SQLUSMALLINT colIndex,
18221716 SQLSMALLINT cType,
@@ -1829,39 +1723,39 @@ static py::object FetchLobColumnData(SQLHSTMT hStmt,
18291723
18301724 while (true ) {
18311725 ++loopCount;
1832-
1833- std::vector<char > chunk (DAE_CHUNK_SIZE, 0 ); // Fill with zeros to handle padding safely
1834- SQLLEN indicator = 0 ;
1835-
1726+ std::vector<char > chunk (DAE_CHUNK_SIZE, 0 );
1727+ SQLLEN actualRead = 0 ;
18361728 ret = SQLGetData_ptr (hStmt,
18371729 colIndex,
18381730 cType,
18391731 chunk.data (),
18401732 DAE_CHUNK_SIZE,
1841- &indicator );
1733+ &actualRead );
18421734
1843- if (indicator == SQL_NULL_DATA) {
1735+ if (ret == SQL_ERROR || !SQL_SUCCEEDED (ret) && ret != SQL_SUCCESS_WITH_INFO) {
1736+ LOG (" Loop {}: Error fetching column {} with cType={}" , loopCount, colIndex, cType);
1737+ ThrowStdException (" Error fetching column data" );
1738+ }
1739+ if (actualRead == SQL_NULL_DATA) {
18441740 LOG (" Loop {}: Column {} is NULL" , loopCount, colIndex);
18451741 return py::none ();
18461742 }
1847- if (!SQL_SUCCEEDED (ret) && ret != SQL_SUCCESS_WITH_INFO) {
1848- LOG (" Loop {}: Error fetching column {} with cType={} ret={}" , loopCount, colIndex, cType, ret);
1849- ThrowStdException (" Error fetching column data" );
1850- }
18511743
18521744 size_t bytesRead = 0 ;
1853-
1854- // Determine how many bytes to process
1855- if (indicator > 0 && indicator != SQL_NO_TOTAL) {
1856- bytesRead = std::min<size_t >(static_cast <size_t >(indicator), DAE_CHUNK_SIZE);
1745+ if (actualRead >= 0 ) {
1746+ bytesRead = static_cast <size_t >(actualRead);
1747+ if (bytesRead > DAE_CHUNK_SIZE) {
1748+ bytesRead = DAE_CHUNK_SIZE;
1749+ }
18571750 } else {
1858- // If unknown, assume full buffer minus possible null terminator padding
1751+ // fallback: use full buffer size if actualRead is unknown
18591752 bytesRead = DAE_CHUNK_SIZE;
18601753 }
18611754
18621755 // For character data, trim trailing null terminators
18631756 if (!isBinary && bytesRead > 0 ) {
18641757 if (!isWideChar) {
1758+ // Narrow characters
18651759 while (bytesRead > 0 && chunk[bytesRead - 1 ] == ' \0 ' ) {
18661760 --bytesRead;
18671761 }
@@ -1872,9 +1766,9 @@ static py::object FetchLobColumnData(SQLHSTMT hStmt,
18721766 // Wide characters
18731767 size_t wcharSize = sizeof (SQLWCHAR);
18741768 if (bytesRead >= wcharSize) {
1875- auto wcharBuf = reinterpret_cast <const SQLWCHAR*>(chunk.data ());
1769+ auto sqlwBuf = reinterpret_cast <const SQLWCHAR*>(chunk.data ());
18761770 size_t wcharCount = bytesRead / wcharSize;
1877- while (wcharCount > 0 && wcharBuf [wcharCount - 1 ] == 0 ) {
1771+ while (wcharCount > 0 && sqlwBuf [wcharCount - 1 ] == 0 ) {
18781772 --wcharCount;
18791773 bytesRead -= wcharSize;
18801774 }
@@ -1884,47 +1778,45 @@ static py::object FetchLobColumnData(SQLHSTMT hStmt,
18841778 }
18851779 }
18861780 }
1887-
18881781 if (bytesRead > 0 ) {
18891782 buffer.insert (buffer.end (), chunk.begin (), chunk.begin () + bytesRead);
18901783 LOG (" Loop {}: Appended {} bytes" , loopCount, bytesRead);
18911784 }
1892-
18931785 if (ret == SQL_SUCCESS) {
18941786 LOG (" Loop {}: SQL_SUCCESS → no more data" , loopCount);
18951787 break ;
18961788 }
18971789 }
1898-
18991790 LOG (" FetchLobColumnData: Total bytes collected = {}" , buffer.size ());
19001791
1901- // If buffer is empty, return empty string or bytes
19021792 if (buffer.empty ()) {
19031793 if (isBinary) {
19041794 return py::bytes (" " );
19051795 }
19061796 return py::str (" " );
19071797 }
1908-
1909- // Convert the collected buffer to appropriate Python type
19101798 if (isWideChar) {
1911- std::wstring wstr = SQLWCHARToWString (reinterpret_cast <const SQLWCHAR*>(buffer.data ()), buffer.size () / sizeof (SQLWCHAR));
1912- LOG (" FetchLobColumnData: Returning wide string of length {}" , wstr.length ());
1913- return py::cast (wstr);
1799+ #if defined(_WIN32)
1800+ std::wstring wstr (reinterpret_cast <const wchar_t *>(buffer.data ()), buffer.size () / sizeof (wchar_t ));
1801+ std::string utf8str = WideToUTF8 (wstr);
1802+ return py::str (utf8str);
1803+ #else
1804+ // Linux/macOS handling
1805+ size_t wcharCount = buffer.size () / sizeof (SQLWCHAR);
1806+ const SQLWCHAR* sqlwBuf = reinterpret_cast <const SQLWCHAR*>(buffer.data ());
1807+ std::string utf8str = SQLWCHARToUTF8String (sqlwBuf, wcharCount);
1808+ return py::str (utf8str);
1809+ #endif
19141810 }
1915-
19161811 if (isBinary) {
19171812 LOG (" FetchLobColumnData: Returning binary of {} bytes" , buffer.size ());
19181813 return py::bytes (buffer.data (), buffer.size ());
19191814 }
1920-
1921- // Default: narrow string
19221815 std::string str (buffer.data (), buffer.size ());
19231816 LOG (" FetchLobColumnData: Returning narrow string of length {}" , str.length ());
19241817 return py::str (str);
19251818}
19261819
1927-
19281820// Helper function to retrieve column data
19291821SQLRETURN SQLGetData_wrap (SqlHandlePtr StatementHandle, SQLUSMALLINT colCount, py::list& row) {
19301822 LOG (" Get data from columns" );
0 commit comments