@@ -3181,14 +3181,28 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
31813181 LOG (" Error while fetching rows in batches" );
31823182 return ret;
31833183 }
3184+ // Pre-cache column metadata to avoid repeated dictionary lookups (major optimization)
3185+ struct ColumnInfo {
3186+ SQLSMALLINT dataType;
3187+ SQLULEN columnSize;
3188+ bool isLob; // Pre-compute LOB status for O(1) lookup
3189+ };
3190+ std::vector<ColumnInfo> columnInfos (numCols);
3191+ for (SQLUSMALLINT col = 0 ; col < numCols; col++) {
3192+ const auto & columnMeta = columnNames[col].cast <py::dict>();
3193+ columnInfos[col].dataType = columnMeta[" DataType" ].cast <SQLSMALLINT>();
3194+ columnInfos[col].columnSize = columnMeta[" ColumnSize" ].cast <SQLULEN>();
3195+ columnInfos[col].isLob = std::find (lobColumns.begin (), lobColumns.end (), col + 1 ) != lobColumns.end (); // col+1 because lobColumns uses 1-based indexing
3196+ }
3197+
31843198 // numRowsFetched is the SQL_ATTR_ROWS_FETCHED_PTR attribute. It'll be populated by
31853199 // SQLFetchScroll
31863200 for (SQLULEN i = 0 ; i < numRowsFetched; i++) {
31873201 py::list row (numCols); // Pre-allocate with known column count for better performance
31883202 for (SQLUSMALLINT col = 1 ; col <= numCols; col++) {
3189- // Cache column metadata lookup for better performance
3190- const auto & columnMeta = columnNames [col - 1 ]. cast <py::dict>() ;
3191- SQLSMALLINT dataType = columnMeta[ " DataType " ]. cast <SQLSMALLINT>() ;
3203+ // Use pre-cached column metadata for optimal performance
3204+ const ColumnInfo& colInfo = columnInfos [col - 1 ];
3205+ SQLSMALLINT dataType = colInfo. dataType ;
31923206 SQLLEN dataLen = buffers.indicators [col - 1 ][i]; if (dataLen == SQL_NULL_DATA) {
31933207 row[col - 1 ] = py::none ();
31943208 continue ;
@@ -3229,11 +3243,11 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
32293243 case SQL_CHAR:
32303244 case SQL_VARCHAR:
32313245 case SQL_LONGVARCHAR: {
3232- SQLULEN columnSize = columnMeta[ " ColumnSize " ]. cast <SQLULEN>() ;
3246+ SQLULEN columnSize = colInfo. columnSize ;
32333247 HandleZeroColumnSizeAtFetch (columnSize);
32343248 uint64_t fetchBufferSize = columnSize + 1 /* null-terminator*/ ;
32353249 uint64_t numCharsInData = dataLen / sizeof (SQLCHAR);
3236- bool isLob = std::find (lobColumns. begin (), lobColumns. end (), col) != lobColumns. end ();
3250+ bool isLob = colInfo. isLob ; // Use cached LOB status for O(1) lookup
32373251 // fetchBufferSize includes null-terminator, numCharsInData doesn't. Hence '<'
32383252 if (!isLob && numCharsInData < fetchBufferSize) {
32393253 // SQLFetch will nullterminate the data
@@ -3249,11 +3263,11 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
32493263 case SQL_WVARCHAR:
32503264 case SQL_WLONGVARCHAR: {
32513265 // TODO: variable length data needs special handling, this logic wont suffice
3252- SQLULEN columnSize = columnMeta[ " ColumnSize " ]. cast <SQLULEN>() ;
3266+ SQLULEN columnSize = colInfo. columnSize ;
32533267 HandleZeroColumnSizeAtFetch (columnSize);
32543268 uint64_t fetchBufferSize = columnSize + 1 /* null-terminator*/ ;
32553269 uint64_t numCharsInData = dataLen / sizeof (SQLWCHAR);
3256- bool isLob = std::find (lobColumns. begin (), lobColumns. end (), col) != lobColumns. end ();
3270+ bool isLob = colInfo. isLob ; // Use cached LOB status for O(1) lookup
32573271 // fetchBufferSize includes null-terminator, numCharsInData doesn't. Hence '<'
32583272 if (!isLob && numCharsInData < fetchBufferSize) {
32593273 // SQLFetch will nullterminate the data
@@ -3411,9 +3425,9 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
34113425 case SQL_BINARY:
34123426 case SQL_VARBINARY:
34133427 case SQL_LONGVARBINARY: {
3414- SQLULEN columnSize = columnMeta[ " ColumnSize " ]. cast <SQLULEN>() ;
3428+ SQLULEN columnSize = colInfo. columnSize ;
34153429 HandleZeroColumnSizeAtFetch (columnSize);
3416- bool isLob = std::find (lobColumns. begin (), lobColumns. end (), col) != lobColumns. end ();
3430+ bool isLob = colInfo. isLob ; // Use cached LOB status for O(1) lookup
34173431 if (!isLob && static_cast <size_t >(dataLen) <= columnSize) {
34183432 row[col - 1 ] = py::bytes (reinterpret_cast <const char *>(
34193433 &buffers.charBuffers [col - 1 ][i * columnSize]),
@@ -3424,6 +3438,7 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
34243438 break ;
34253439 }
34263440 default : {
3441+ const auto & columnMeta = columnNames[col - 1 ].cast <py::dict>(); // Re-fetch for error case only
34273442 std::wstring columnName = columnMeta[" ColumnName" ].cast <std::wstring>();
34283443 std::ostringstream errorString;
34293444 errorString << " Unsupported data type for column - " << columnName.c_str ()
0 commit comments