Skip to content

Commit 1a68058

Browse files
committed
latest
1 parent 7a05ed6 commit 1a68058

File tree

5 files changed

+67
-108
lines changed

5 files changed

+67
-108
lines changed

main.py

Lines changed: 2 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -157,46 +157,10 @@
157157
cursor.close()
158158
conn.close()
159159

160-
print("Using mssql-python now (original)")
160+
print("Using mssql-python now")
161161
start_time = time.perf_counter()
162162
conn = connect(conn_str)
163-
cursor = conn.cursor()
164-
cursor.execute(COMPLEX_JOIN_AGGREGATION)
165-
rows = cursor.fetchall()
166-
167-
end_time = time.perf_counter()
168-
elapsed_time = end_time - start_time
169-
print(f"Elapsed time in mssql-python (original) for query 1: {elapsed_time:.4f} seconds")
170-
171-
start_time = time.perf_counter()
172-
cursor.execute(LARGE_DATASET)
173-
rows = cursor.fetchall()
174-
end_time = time.perf_counter()
175-
elapsed_time = end_time - start_time
176-
print(f"Elapsed time in mssql-python (original) for query 2: {elapsed_time:.4f} seconds")
177-
178-
start_time = time.perf_counter()
179-
cursor.execute(VERY_LARGE_DATASET)
180-
rows = cursor.fetchall()
181-
end_time = time.perf_counter()
182-
elapsed_time = end_time - start_time
183-
print(f"Elapsed time in mssql-python (original) for query 3: {elapsed_time:.4f} seconds")
184-
185-
start_time = time.perf_counter()
186-
cursor.execute(SUBQUERY_WITH_CTE)
187-
rows = cursor.fetchall()
188-
end_time = time.perf_counter()
189-
elapsed_time = end_time - start_time
190-
print(f"Elapsed time in mssql-python (original) for query 4: {elapsed_time:.4f} seconds")
191-
192-
cursor.close()
193-
conn.close()
194-
195-
print("Using mssql-python now (optimized)")
196-
start_time = time.perf_counter()
197-
conn = connect(conn_str)
198-
conn.enable_performance_mode() # Enable connection-level performance mode
199-
cursor = conn.cursor() # This will automatically apply optimizations
163+
cursor = conn.cursor() # Performance optimizations are now enabled by default
200164
cursor.execute(COMPLEX_JOIN_AGGREGATION)
201165
rows = cursor.fetchall()
202166

mssql_python/connection.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,7 @@ def __init__(self, connection_str: str = "", autocommit: bool = False, attrs_bef
203203
self._conn = ddbc_bindings.Connection(self.connection_str, self._pooling, self._attrs_before)
204204
self.setautocommit(autocommit)
205205

206-
# Performance optimization settings
207-
self._performance_mode = False # When enabled, applies aggressive optimizations
206+
# Performance optimizations are now enabled by default
208207

209208
def _construct_connection_string(self, connection_str: str = "", **kwargs) -> str:
210209
"""
@@ -599,10 +598,7 @@ def cursor(self) -> Cursor:
599598

600599
cursor = Cursor(self, timeout=self._timeout)
601600

602-
# Apply performance optimizations if enabled
603-
if self._performance_mode:
604-
cursor.optimize_for_performance()
605-
cursor.enable_fast_mode() # Enable fast mode when performance mode is on
601+
# Performance optimizations are now enabled by default in cursor initialization
606602

607603
self._cursors.add(cursor) # Track the cursor
608604
return cursor
@@ -1245,26 +1241,25 @@ def __exit__(self, *args) -> None:
12451241
if not self._closed:
12461242
self.close()
12471243

1244+
# Performance optimizations are now enabled by default
1245+
# Legacy methods kept for backward compatibility
12481246
def enable_performance_mode(self):
12491247
"""
1250-
Enable performance mode for all cursors created from this connection.
1251-
This applies optimizations that prioritize speed over some features:
1252-
- Returns tuples instead of Row objects
1253-
- Increases batch sizes for better throughput
1254-
- Caches column mappings
1255-
- Uses aggressive arraysize settings
1248+
Performance optimizations are now enabled by default.
1249+
This method is kept for backward compatibility.
12561250
"""
1257-
self._performance_mode = True
1251+
pass # No-op since performance mode is always on
12581252

12591253
def disable_performance_mode(self):
12601254
"""
1261-
Disable performance mode and return to standard behavior.
1255+
Performance optimizations are now enabled by default and cannot be disabled.
1256+
This method is kept for backward compatibility.
12621257
"""
1263-
self._performance_mode = False
1258+
pass # No-op since we want to keep performance optimizations
12641259

12651260
def is_performance_mode_enabled(self):
1266-
"""Check if performance mode is enabled."""
1267-
return self._performance_mode
1261+
"""Performance mode is always enabled now."""
1262+
return True # Always return True since optimizations are default
12681263

12691264
def __del__(self):
12701265
"""

mssql_python/cursor.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def __init__(self, connection, timeout: int = 0) -> None:
8181
self.description = None
8282
self.rowcount = -1
8383
self.arraysize = (
84-
1000 # Increased default for better performance - user can still change it
84+
5000 # Optimized default for better performance - sweet spot between speed and memory
8585
)
8686
self.buffer_length = 1024 # Default buffer length for string data
8787
self.closed = False
@@ -99,8 +99,8 @@ def __init__(self, connection, timeout: int = 0) -> None:
9999
# rownumber attribute
100100
self._rownumber = -1 # DB-API extension: last returned row index, -1 before first
101101

102-
# Performance optimizations
103-
self._fast_mode = False # When enabled, returns tuples instead of Row objects
102+
# Performance optimizations (enabled by default)
103+
self._fast_mode = True # Returns tuples instead of Row objects for better performance
104104
self._cached_column_map = None # Cache column mapping for performance
105105
self._next_row_index = 0 # internal: index of the next row the driver will return (0-based)
106106
self._has_result_set = False # Track if we have an active result set

mssql_python/msvcp140.dll

562 KB
Binary file not shown.

mssql_python/pybind/ddbc_bindings.cpp

Lines changed: 50 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3184,38 +3184,38 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
31843184
// numRowsFetched is the SQL_ATTR_ROWS_FETCHED_PTR attribute. It'll be populated by
31853185
// SQLFetchScroll
31863186
for (SQLULEN i = 0; i < numRowsFetched; i++) {
3187-
py::list row;
3187+
py::list row(numCols); // Pre-allocate with known column count for better performance
31883188
for (SQLUSMALLINT col = 1; col <= numCols; col++) {
31893189
// Cache column metadata lookup for better performance
31903190
const auto& columnMeta = columnNames[col - 1].cast<py::dict>();
31913191
SQLSMALLINT dataType = columnMeta["DataType"].cast<SQLSMALLINT>();
31923192
SQLLEN dataLen = buffers.indicators[col - 1][i]; if (dataLen == SQL_NULL_DATA) {
3193-
row.append(py::none());
3193+
row[col - 1] = py::none();
31943194
continue;
31953195
}
31963196
// TODO: variable length data needs special handling, this logic wont suffice
31973197
// This value indicates that the driver cannot determine the length of the data
31983198
if (dataLen == SQL_NO_TOTAL) {
31993199
LOG("Cannot determine the length of the data. Returning NULL value instead."
32003200
"Column ID - {}", col);
3201-
row.append(py::none());
3201+
row[col - 1] = py::none();
32023202
continue;
32033203
} else if (dataLen == SQL_NULL_DATA) {
3204-
LOG("Column data is NULL. Appending None to the result row. Column ID - {}", col);
3205-
row.append(py::none());
3204+
LOG("Column data is NULL. Setting None to the result row. Column ID - {}", col);
3205+
row[col - 1] = py::none();
32063206
continue;
32073207
} else if (dataLen == 0) {
32083208
// Handle zero-length (non-NULL) data
32093209
if (dataType == SQL_CHAR || dataType == SQL_VARCHAR || dataType == SQL_LONGVARCHAR) {
3210-
row.append(std::string(""));
3210+
row[col - 1] = std::string("");
32113211
} else if (dataType == SQL_WCHAR || dataType == SQL_WVARCHAR || dataType == SQL_WLONGVARCHAR) {
3212-
row.append(std::wstring(L""));
3212+
row[col - 1] = std::wstring(L"");
32133213
} else if (dataType == SQL_BINARY || dataType == SQL_VARBINARY || dataType == SQL_LONGVARBINARY) {
3214-
row.append(py::bytes(""));
3214+
row[col - 1] = py::bytes("");
32153215
} else {
3216-
// For other datatypes, 0 length is unexpected. Log & append None
3217-
LOG("Column data length is 0 for non-string/binary datatype. Appending None to the result row. Column ID - {}", col);
3218-
row.append(py::none());
3216+
// For other datatypes, 0 length is unexpected. Log & set None
3217+
LOG("Column data length is 0 for non-string/binary datatype. Setting None to the result row. Column ID - {}", col);
3218+
row[col - 1] = py::none();
32193219
}
32203220
continue;
32213221
} else if (dataLen < 0) {
@@ -3237,11 +3237,11 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
32373237
// fetchBufferSize includes null-terminator, numCharsInData doesn't. Hence '<'
32383238
if (!isLob && numCharsInData < fetchBufferSize) {
32393239
// SQLFetch will nullterminate the data
3240-
row.append(std::string(
3240+
row[col - 1] = std::string(
32413241
reinterpret_cast<char*>(&buffers.charBuffers[col - 1][i * fetchBufferSize]),
3242-
numCharsInData));
3242+
numCharsInData);
32433243
} else {
3244-
row.append(FetchLobColumnData(hStmt, col, SQL_C_CHAR, false, false));
3244+
row[col - 1] = FetchLobColumnData(hStmt, col, SQL_C_CHAR, false, false);
32453245
}
32463246
break;
32473247
}
@@ -3261,36 +3261,36 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
32613261
// Use unix-specific conversion to handle the wchar_t/SQLWCHAR size difference
32623262
SQLWCHAR* wcharData = &buffers.wcharBuffers[col - 1][i * fetchBufferSize];
32633263
std::wstring wstr = SQLWCHARToWString(wcharData, numCharsInData);
3264-
row.append(wstr);
3264+
row[col - 1] = wstr;
32653265
#else
32663266
// On Windows, wchar_t and SQLWCHAR are both 2 bytes, so direct cast works
3267-
row.append(std::wstring(
3267+
row[col - 1] = std::wstring(
32683268
reinterpret_cast<wchar_t*>(&buffers.wcharBuffers[col - 1][i * fetchBufferSize]),
3269-
numCharsInData));
3269+
numCharsInData);
32703270
#endif
32713271
} else {
3272-
row.append(FetchLobColumnData(hStmt, col, SQL_C_WCHAR, true, false));
3272+
row[col - 1] = FetchLobColumnData(hStmt, col, SQL_C_WCHAR, true, false);
32733273
}
32743274
break;
32753275
}
32763276
case SQL_INTEGER: {
3277-
row.append(buffers.intBuffers[col - 1][i]);
3277+
row[col - 1] = buffers.intBuffers[col - 1][i];
32783278
break;
32793279
}
32803280
case SQL_SMALLINT: {
3281-
row.append(buffers.smallIntBuffers[col - 1][i]);
3281+
row[col - 1] = buffers.smallIntBuffers[col - 1][i];
32823282
break;
32833283
}
32843284
case SQL_TINYINT: {
3285-
row.append(buffers.charBuffers[col - 1][i]);
3285+
row[col - 1] = buffers.charBuffers[col - 1][i];
32863286
break;
32873287
}
32883288
case SQL_BIT: {
3289-
row.append(static_cast<bool>(buffers.charBuffers[col - 1][i]));
3289+
row[col - 1] = static_cast<bool>(buffers.charBuffers[col - 1][i]);
32903290
break;
32913291
}
32923292
case SQL_REAL: {
3293-
row.append(buffers.realBuffers[col - 1][i]);
3293+
row[col - 1] = buffers.realBuffers[col - 1][i];
32943294
break;
32953295
}
32963296
case SQL_DECIMAL:
@@ -3313,47 +3313,47 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
33133313
}
33143314

33153315
// Convert to Python decimal using cached class (keep this optimization)
3316-
row.append(PythonObjectCache::get_decimal_class()(numStr));
3316+
row[col - 1] = PythonObjectCache::get_decimal_class()(numStr);
33173317
} catch (const py::error_already_set& e) {
3318-
// Handle the exception, e.g., log the error and append py::none()
3318+
// Handle the exception, e.g., log the error and set py::none()
33193319
LOG("Error converting to decimal: {}", e.what());
3320-
row.append(py::none());
3320+
row[col - 1] = py::none();
33213321
}
33223322
break;
33233323
}
33243324
case SQL_DOUBLE:
33253325
case SQL_FLOAT: {
3326-
row.append(buffers.doubleBuffers[col - 1][i]);
3326+
row[col - 1] = buffers.doubleBuffers[col - 1][i];
33273327
break;
33283328
}
33293329
case SQL_TIMESTAMP:
33303330
case SQL_TYPE_TIMESTAMP:
33313331
case SQL_DATETIME: {
3332-
row.append(PythonObjectCache::get_datetime_class()(buffers.timestampBuffers[col - 1][i].year,
3333-
buffers.timestampBuffers[col - 1][i].month,
3334-
buffers.timestampBuffers[col - 1][i].day,
3335-
buffers.timestampBuffers[col - 1][i].hour,
3336-
buffers.timestampBuffers[col - 1][i].minute,
3337-
buffers.timestampBuffers[col - 1][i].second,
3338-
buffers.timestampBuffers[col - 1][i].fraction / 1000 /* Convert back ns to µs */));
3332+
row[col - 1] = PythonObjectCache::get_datetime_class()(buffers.timestampBuffers[col - 1][i].year,
3333+
buffers.timestampBuffers[col - 1][i].month,
3334+
buffers.timestampBuffers[col - 1][i].day,
3335+
buffers.timestampBuffers[col - 1][i].hour,
3336+
buffers.timestampBuffers[col - 1][i].minute,
3337+
buffers.timestampBuffers[col - 1][i].second,
3338+
buffers.timestampBuffers[col - 1][i].fraction / 1000 /* Convert back ns to µs */);
33393339
break;
33403340
}
33413341
case SQL_BIGINT: {
3342-
row.append(buffers.bigIntBuffers[col - 1][i]);
3342+
row[col - 1] = buffers.bigIntBuffers[col - 1][i];
33433343
break;
33443344
}
33453345
case SQL_TYPE_DATE: {
3346-
row.append(PythonObjectCache::get_date_class()(buffers.dateBuffers[col - 1][i].year,
3347-
buffers.dateBuffers[col - 1][i].month,
3348-
buffers.dateBuffers[col - 1][i].day));
3346+
row[col - 1] = PythonObjectCache::get_date_class()(buffers.dateBuffers[col - 1][i].year,
3347+
buffers.dateBuffers[col - 1][i].month,
3348+
buffers.dateBuffers[col - 1][i].day);
33493349
break;
33503350
}
33513351
case SQL_TIME:
33523352
case SQL_TYPE_TIME:
33533353
case SQL_SS_TIME2: {
3354-
row.append(PythonObjectCache::get_time_class()(buffers.timeBuffers[col - 1][i].hour,
3355-
buffers.timeBuffers[col - 1][i].minute,
3356-
buffers.timeBuffers[col - 1][i].second));
3354+
row[col - 1] = PythonObjectCache::get_time_class()(buffers.timeBuffers[col - 1][i].hour,
3355+
buffers.timeBuffers[col - 1][i].minute,
3356+
buffers.timeBuffers[col - 1][i].second);
33573357
break;
33583358
}
33593359
case SQL_SS_TIMESTAMPOFFSET: {
@@ -3377,16 +3377,16 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
33773377
tzinfo
33783378
);
33793379
py_dt = py_dt.attr("astimezone")(datetime.attr("timezone").attr("utc"));
3380-
row.append(py_dt);
3380+
row[col - 1] = py_dt;
33813381
} else {
3382-
row.append(py::none());
3382+
row[col - 1] = py::none();
33833383
}
33843384
break;
33853385
}
33863386
case SQL_GUID: {
33873387
SQLLEN indicator = buffers.indicators[col - 1][i];
33883388
if (indicator == SQL_NULL_DATA) {
3389-
row.append(py::none());
3389+
row[col - 1] = py::none();
33903390
break;
33913391
}
33923392
SQLGUID* guidValue = &buffers.guidBuffers[col - 1][i];
@@ -3405,7 +3405,7 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
34053405
py::dict kwargs;
34063406
kwargs["bytes"] = py_guid_bytes;
34073407
py::object uuid_obj = py::module_::import("uuid").attr("UUID")(**kwargs);
3408-
row.append(uuid_obj);
3408+
row[col - 1] = uuid_obj;
34093409
break;
34103410
}
34113411
case SQL_BINARY:
@@ -3415,11 +3415,11 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
34153415
HandleZeroColumnSizeAtFetch(columnSize);
34163416
bool isLob = std::find(lobColumns.begin(), lobColumns.end(), col) != lobColumns.end();
34173417
if (!isLob && static_cast<size_t>(dataLen) <= columnSize) {
3418-
row.append(py::bytes(reinterpret_cast<const char*>(
3419-
&buffers.charBuffers[col - 1][i * columnSize]),
3420-
dataLen));
3418+
row[col - 1] = py::bytes(reinterpret_cast<const char*>(
3419+
&buffers.charBuffers[col - 1][i * columnSize]),
3420+
dataLen);
34213421
} else {
3422-
row.append(FetchLobColumnData(hStmt, col, SQL_C_BINARY, false, true));
3422+
row[col - 1] = FetchLobColumnData(hStmt, col, SQL_C_BINARY, false, true);
34233423
}
34243424
break;
34253425
}
@@ -3434,7 +3434,7 @@ SQLRETURN FetchBatchData(SQLHSTMT hStmt, ColumnBuffers& buffers, py::list& colum
34343434
}
34353435
}
34363436
}
3437-
rows.append(row);
3437+
rows.append(row); // TODO: Could be optimized with pre-allocation if we knew total row count
34383438
}
34393439
return ret;
34403440
}

0 commit comments

Comments
 (0)