Skip to content

Commit b7b5c9c

Browse files
committed
Merge branch 'main' of https://github.com/microsoft/mssql-python into bewithgaurav/universal_codecov
2 parents d844473 + d94b518 commit b7b5c9c

File tree

14 files changed

+5855
-180
lines changed

14 files changed

+5855
-180
lines changed

PyPI_Description.md

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,15 @@ PyBind11 provides:
3939

4040
We are currently in **Public Preview**.
4141

42-
## What's new in v0.10.0
43-
44-
- **SUSE Linux Support:** Added full support for SUSE and openSUSE distributions alongside existing other Linux distros support, broadening enterprise Linux compatibility.
45-
- **Context Manager Support:** Implemented Python `with` statement support for Connection and Cursor classes with automatic transaction management and resource cleanup.
46-
- **Large Text Streaming:** Added Data At Execution (DAE) support for streaming large text parameters (`NVARCHAR(MAX)`, `VARCHAR(MAX)`), eliminating memory constraints for bulk text `execute()` operations.
47-
- `VARBINARY(MAX)` support to follow alongwith streaming support for fetch operations.
48-
- **Enhanced Unicode Handling:** Improved emoji and international character support with robust UTF-16 encoding for reliable multilingual data processing.
49-
- **PyODBC Compatibility:** Enhanced API compatibility with pyodbc including:
50-
- DB-API 2.0 exception classes: `Warning`, `Error`, `InterfaceError`, `DatabaseError`, `DataError`, `OperationalError`, `IntegrityError`, `InternalError`, `ProgrammingError`, `NotSupportedError`
51-
- Context manager support with `with` statements for Connection and Cursor
52-
- Encoding configuration APIs: `setencoding()`, `getencoding()`, `setdecoding()`, `getdecoding()`
53-
- Cursor navigation APIs: `next()`, `__iter__()`, `scroll()`, `skip()`, `fetchval()`
54-
- Cursor attributes: `rownumber`, `messages`
55-
- Additional methods: `cursor.commit()`, `cursor.rollback()`, `table()`
42+
## What's new in v0.11.0
43+
44+
- **Database Metadata & Introspection:** Added comprehensive `getInfo()` method and extensive catalog APIs (`SQLGetTypeInfo`, `SQLProcedures`, `SQLForeignKeys`, `SQLColumns`) for complete database schema discovery and introspection capabilities.
45+
- **Advanced Parameter Management:** Implemented `setinputsizes()` with SQL type constants and enhanced parameter validation through the `SQLTypes` class for precise type control in parameterized queries.
46+
- **Large Data Streaming Enhancements:** Extended streaming support to VARBINARY(MAX) and VARCHAR(MAX) across all fetch operations (`fetchone`, `fetchmany`, `fetchall`) with improved chunked retrieval for efficient memory usage.
47+
- **Output Data Conversion System:** Introduced flexible output converter framework with `add_output_converter()`, `remove_output_converter()`, and related methods for customizable data transformations during result fetching.
48+
- **Connection-Level Execute:** Added direct `execute()` method to Connection class for streamlined query execution without explicit cursor management.
49+
- **Financial Data Type Support:** Comprehensive support for SQL Server MONEY and SMALLMONEY types with proper boundary value handling and decimal precision.
50+
- **Enhanced Configuration APIs:** Added connection timeout control, decimal separator configuration (`getDecimalSeperator()`, `setDecimalSeperator()`), and improved global variable management.
5651

5752
For more information, please visit the project link on Github: https://github.com/microsoft/mssql-python
5853

mssql_python/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,28 @@ def _custom_setattr(name, value):
180180

181181
# Replace the module's __setattr__ with our custom version
182182
sys.modules[__name__].__setattr__ = _custom_setattr
183+
184+
185+
# Export SQL constants at module level
186+
SQL_CHAR = ConstantsDDBC.SQL_CHAR.value
187+
SQL_VARCHAR = ConstantsDDBC.SQL_VARCHAR.value
188+
SQL_LONGVARCHAR = ConstantsDDBC.SQL_LONGVARCHAR.value
189+
SQL_WCHAR = ConstantsDDBC.SQL_WCHAR.value
190+
SQL_WVARCHAR = ConstantsDDBC.SQL_WVARCHAR.value
191+
SQL_WLONGVARCHAR = ConstantsDDBC.SQL_WLONGVARCHAR.value
192+
SQL_DECIMAL = ConstantsDDBC.SQL_DECIMAL.value
193+
SQL_NUMERIC = ConstantsDDBC.SQL_NUMERIC.value
194+
SQL_BIT = ConstantsDDBC.SQL_BIT.value
195+
SQL_TINYINT = ConstantsDDBC.SQL_TINYINT.value
196+
SQL_SMALLINT = ConstantsDDBC.SQL_SMALLINT.value
197+
SQL_INTEGER = ConstantsDDBC.SQL_INTEGER.value
198+
SQL_BIGINT = ConstantsDDBC.SQL_BIGINT.value
199+
SQL_REAL = ConstantsDDBC.SQL_REAL.value
200+
SQL_FLOAT = ConstantsDDBC.SQL_FLOAT.value
201+
SQL_DOUBLE = ConstantsDDBC.SQL_DOUBLE.value
202+
SQL_BINARY = ConstantsDDBC.SQL_BINARY.value
203+
SQL_VARBINARY = ConstantsDDBC.SQL_VARBINARY.value
204+
SQL_LONGVARBINARY = ConstantsDDBC.SQL_LONGVARBINARY.value
205+
SQL_DATE = ConstantsDDBC.SQL_DATE.value
206+
SQL_TIME = ConstantsDDBC.SQL_TIME.value
207+
SQL_TIMESTAMP = ConstantsDDBC.SQL_TIMESTAMP.value

mssql_python/connection.py

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ def _validate_encoding(encoding: str) -> bool:
6666
ProgrammingError,
6767
NotSupportedError,
6868
)
69+
from mssql_python.constants import GetInfoConstants
6970

7071

7172
class Connection:
@@ -121,7 +122,7 @@ class Connection:
121122
ProgrammingError = ProgrammingError
122123
NotSupportedError = NotSupportedError
123124

124-
def __init__(self, connection_str: str = "", autocommit: bool = False, attrs_before: dict = None, **kwargs) -> None:
125+
def __init__(self, connection_str: str = "", autocommit: bool = False, attrs_before: dict = None, timeout: int = 0, **kwargs) -> None:
125126
"""
126127
Initialize the connection object with the specified connection string and parameters.
127128
@@ -180,6 +181,7 @@ def __init__(self, connection_str: str = "", autocommit: bool = False, attrs_bef
180181
self._attrs_before.update(connection_result[1])
181182

182183
self._closed = False
184+
self._timeout = timeout
183185

184186
# Using WeakSet which automatically removes cursors when they are no longer in use
185187
# It is a set that holds weak references to its elements.
@@ -236,6 +238,39 @@ def _construct_connection_string(self, connection_str: str = "", **kwargs) -> st
236238

237239
return conn_str
238240

241+
@property
242+
def timeout(self) -> int:
243+
"""
244+
Get the current query timeout setting in seconds.
245+
246+
Returns:
247+
int: The timeout value in seconds. Zero means no timeout (wait indefinitely).
248+
"""
249+
return self._timeout
250+
251+
@timeout.setter
252+
def timeout(self, value: int) -> None:
253+
"""
254+
Set the query timeout for all operations performed by this connection.
255+
256+
Args:
257+
value (int): The timeout value in seconds. Zero means no timeout.
258+
259+
Returns:
260+
None
261+
262+
Note:
263+
This timeout applies to all cursors created from this connection.
264+
It cannot be changed for individual cursors or SQL statements.
265+
If a query timeout occurs, an OperationalError exception will be raised.
266+
"""
267+
if not isinstance(value, int):
268+
raise TypeError("Timeout must be an integer")
269+
if value < 0:
270+
raise ValueError("Timeout cannot be negative")
271+
self._timeout = value
272+
log('info', f"Query timeout set to {value} seconds")
273+
239274
@property
240275
def autocommit(self) -> bool:
241276
"""
@@ -510,6 +545,30 @@ def getdecoding(self, sqltype):
510545

511546
return self._decoding_settings[sqltype].copy()
512547

548+
@property
549+
def searchescape(self):
550+
"""
551+
The ODBC search pattern escape character, as returned by
552+
SQLGetInfo(SQL_SEARCH_PATTERN_ESCAPE), used to escape special characters
553+
such as '%' and '_' in LIKE clauses. These are driver specific.
554+
555+
Returns:
556+
str: The search pattern escape character (usually '\' or another character)
557+
"""
558+
if not hasattr(self, '_searchescape'):
559+
try:
560+
escape_char = self.getinfo(GetInfoConstants.SQL_SEARCH_PATTERN_ESCAPE.value)
561+
# Some drivers might return this as an integer memory address
562+
# or other non-string format, so ensure we have a string
563+
if not isinstance(escape_char, str):
564+
escape_char = '\\' # Default to backslash if not a string
565+
self._searchescape = escape_char
566+
except Exception as e:
567+
# Log the exception for debugging, but do not expose sensitive info
568+
log('warning', f"Failed to retrieve search escape character, using default '\\'. Exception: {type(e).__name__}")
569+
self._searchescape = '\\'
570+
return self._searchescape
571+
513572
def cursor(self) -> Cursor:
514573
"""
515574
Return a new Cursor object using the connection.
@@ -533,7 +592,7 @@ def cursor(self) -> Cursor:
533592
ddbc_error="Cannot create cursor on closed connection",
534593
)
535594

536-
cursor = Cursor(self)
595+
cursor = Cursor(self, timeout=self._timeout)
537596
self._cursors.add(cursor) # Track the cursor
538597
return cursor
539598

@@ -790,6 +849,30 @@ def batch_execute(self, statements, params=None, reuse_cursor=None, auto_close=F
790849
log('debug', "Automatically closed cursor after batch execution")
791850

792851
return results, cursor
852+
853+
def getinfo(self, info_type):
854+
"""
855+
Return general information about the driver and data source.
856+
857+
Args:
858+
info_type (int): The type of information to return. See the ODBC
859+
SQLGetInfo documentation for the supported values.
860+
861+
Returns:
862+
The requested information. The type of the returned value depends
863+
on the information requested. It will be a string, integer, or boolean.
864+
865+
Raises:
866+
DatabaseError: If there is an error retrieving the information.
867+
InterfaceError: If the connection is closed.
868+
"""
869+
if self._closed:
870+
raise InterfaceError(
871+
driver_error="Cannot get info on closed connection",
872+
ddbc_error="Cannot get info on closed connection",
873+
)
874+
875+
return self._conn.get_info(info_type)
793876

794877
def commit(self) -> None:
795878
"""

0 commit comments

Comments
 (0)