Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions mssql_python/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,30 @@ def cursor(self) -> Cursor:
cursor = Cursor(self)
self._cursors.add(cursor) # Track the cursor
return cursor

def getinfo(self, info_type):
"""
Return general information about the driver and data source.

Args:
info_type (int): The type of information to return. See the ODBC
SQLGetInfo documentation for the supported values.

Returns:
The requested information. The type of the returned value depends
on the information requested. It will be a string, integer, or boolean.

Raises:
DatabaseError: If there is an error retrieving the information.
InterfaceError: If the connection is closed.
"""
if self._closed:
raise InterfaceError(
driver_error="Cannot get info on closed connection",
ddbc_error="Cannot get info on closed connection",
)

return self._conn.get_info(info_type)

def commit(self) -> None:
"""
Expand Down
138 changes: 138 additions & 0 deletions mssql_python/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,144 @@ class ConstantsDDBC(Enum):
SQL_NULLABLE = 1
SQL_MAX_NUMERIC_LEN = 16

class GetInfoConstants(Enum):
"""
These constants are used with various methods like getinfo().
"""

# Driver and database information
SQL_DRIVER_NAME = 6
SQL_DRIVER_VER = 7
SQL_DRIVER_ODBC_VER = 77
SQL_DRIVER_HLIB = 76
SQL_DRIVER_HENV = 75
SQL_DRIVER_HDBC = 74
SQL_DATA_SOURCE_NAME = 2
SQL_DATABASE_NAME = 16
SQL_SERVER_NAME = 13
SQL_USER_NAME = 47

# SQL conformance and support
SQL_SQL_CONFORMANCE = 118
SQL_KEYWORDS = 89
SQL_IDENTIFIER_CASE = 28
SQL_IDENTIFIER_QUOTE_CHAR = 29
SQL_SPECIAL_CHARACTERS = 94
SQL_SQL92_ENTRY_SQL = 127
SQL_SQL92_INTERMEDIATE_SQL = 128
SQL_SQL92_FULL_SQL = 129
SQL_SUBQUERIES = 95
SQL_EXPRESSIONS_IN_ORDERBY = 27
SQL_CORRELATION_NAME = 74
SQL_SEARCH_PATTERN_ESCAPE = 14

# Catalog and schema support
SQL_CATALOG_TERM = 42
SQL_CATALOG_NAME_SEPARATOR = 41
SQL_SCHEMA_TERM = 39
SQL_TABLE_TERM = 45
SQL_PROCEDURES = 21
SQL_ACCESSIBLE_TABLES = 19
SQL_ACCESSIBLE_PROCEDURES = 20
SQL_CATALOG_NAME = 10002
SQL_CATALOG_USAGE = 92
SQL_SCHEMA_USAGE = 91
SQL_COLUMN_ALIAS = 87
SQL_DESCRIBE_PARAMETER = 10002

# Transaction support
SQL_TXN_CAPABLE = 46
SQL_TXN_ISOLATION_OPTION = 72
SQL_DEFAULT_TXN_ISOLATION = 26
SQL_MULTIPLE_ACTIVE_TXN = 37
SQL_TXN_ISOLATION_LEVEL = 108

# Data type support
SQL_NUMERIC_FUNCTIONS = 49
SQL_STRING_FUNCTIONS = 50
SQL_DATETIME_FUNCTIONS = 51
SQL_SYSTEM_FUNCTIONS = 58
SQL_CONVERT_FUNCTIONS = 48
SQL_LIKE_ESCAPE_CLAUSE = 113

# Numeric limits
SQL_MAX_COLUMN_NAME_LEN = 30
SQL_MAX_TABLE_NAME_LEN = 35
SQL_MAX_SCHEMA_NAME_LEN = 32
SQL_MAX_CATALOG_NAME_LEN = 34
SQL_MAX_IDENTIFIER_LEN = 10005
SQL_MAX_STATEMENT_LEN = 105
SQL_MAX_CHAR_LITERAL_LEN = 108
SQL_MAX_BINARY_LITERAL_LEN = 112
SQL_MAX_COLUMNS_IN_TABLE = 101
SQL_MAX_COLUMNS_IN_SELECT = 100
SQL_MAX_COLUMNS_IN_GROUP_BY = 97
SQL_MAX_COLUMNS_IN_ORDER_BY = 99
SQL_MAX_COLUMNS_IN_INDEX = 98
SQL_MAX_TABLES_IN_SELECT = 106
SQL_MAX_CONCURRENT_ACTIVITIES = 1
SQL_MAX_DRIVER_CONNECTIONS = 0
SQL_MAX_ROW_SIZE = 104
SQL_MAX_USER_NAME_LEN = 107

# Connection attributes
SQL_ACTIVE_CONNECTIONS = 0
SQL_ACTIVE_STATEMENTS = 1
SQL_DATA_SOURCE_READ_ONLY = 25
SQL_NEED_LONG_DATA_LEN = 111
SQL_GETDATA_EXTENSIONS = 81

# Result set and cursor attributes
SQL_CURSOR_COMMIT_BEHAVIOR = 23
SQL_CURSOR_ROLLBACK_BEHAVIOR = 24
SQL_CURSOR_SENSITIVITY = 10001
SQL_BOOKMARK_PERSISTENCE = 82
SQL_DYNAMIC_CURSOR_ATTRIBUTES1 = 144
SQL_DYNAMIC_CURSOR_ATTRIBUTES2 = 145
SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES1 = 146
SQL_FORWARD_ONLY_CURSOR_ATTRIBUTES2 = 147
SQL_STATIC_CURSOR_ATTRIBUTES1 = 150
SQL_STATIC_CURSOR_ATTRIBUTES2 = 151
SQL_KEYSET_CURSOR_ATTRIBUTES1 = 148
SQL_KEYSET_CURSOR_ATTRIBUTES2 = 149
SQL_SCROLL_OPTIONS = 44
SQL_SCROLL_CONCURRENCY = 43
SQL_FETCH_DIRECTION = 8
SQL_ROWSET_SIZE = 9
SQL_CONCURRENCY = 7
SQL_ROW_NUMBER = 14
SQL_STATIC_SENSITIVITY = 83
SQL_BATCH_SUPPORT = 121
SQL_BATCH_ROW_COUNT = 120
SQL_PARAM_ARRAY_ROW_COUNTS = 153
SQL_PARAM_ARRAY_SELECTS = 154

# Positioned statement support
SQL_POSITIONED_STATEMENTS = 80

# Other constants
SQL_GROUP_BY = 88
SQL_OJ_CAPABILITIES = 65
SQL_ORDER_BY_COLUMNS_IN_SELECT = 90
SQL_OUTER_JOINS = 38
SQL_QUOTED_IDENTIFIER_CASE = 93
SQL_CONCAT_NULL_BEHAVIOR = 22
SQL_NULL_COLLATION = 85
SQL_ALTER_TABLE = 86
SQL_UNION = 96
SQL_DDL_INDEX = 170
SQL_MULT_RESULT_SETS = 36
SQL_OWNER_USAGE = 91
SQL_QUALIFIER_USAGE = 92
SQL_TIMEDATE_ADD_INTERVALS = 109
SQL_TIMEDATE_DIFF_INTERVALS = 110

# Return values for some getinfo functions
SQL_IC_UPPER = 1
SQL_IC_LOWER = 2
SQL_IC_SENSITIVE = 3
SQL_IC_MIXED = 4

class AuthType(Enum):
"""Constants for authentication types"""
INTERACTIVE = "activedirectoryinteractive"
Expand Down
107 changes: 107 additions & 0 deletions mssql_python/pybind/connection/connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -314,4 +314,111 @@ SqlHandlePtr ConnectionHandle::allocStatementHandle() {
ThrowStdException("Connection object is not initialized");
}
return _conn->allocStatementHandle();
}

py::object Connection::getInfo(SQLUSMALLINT infoType) const {
if (!_dbcHandle) {
ThrowStdException("Connection handle not allocated");
}

LOG("Getting connection info for type {}", infoType);

// For string results - allocate a buffer
char charBuffer[1024] = {0};
SQLSMALLINT stringLength = 0;
SQLRETURN ret;

// First try to get the info as a string or binary data
ret = SQLGetInfo_ptr(_dbcHandle->get(), infoType, charBuffer, sizeof(charBuffer), &stringLength);
if (!SQL_SUCCEEDED(ret)) {
checkError(ret);
}

// Determine return type based on the InfoType
// String types usually have InfoType > 10000
if (infoType > 10000 ||
infoType == SQL_DATA_SOURCE_NAME ||
infoType == SQL_DBMS_NAME ||
infoType == SQL_DBMS_VER ||
infoType == SQL_DRIVER_NAME ||
infoType == SQL_DRIVER_VER) {
// Return as string
return py::str(charBuffer);
}
else if (infoType == SQL_DRIVER_ODBC_VER ||
infoType == SQL_SERVER_NAME) {
// Return as string
return py::str(charBuffer);
}
else {
// For numeric types, we need to interpret the buffer based on the expected return type
// Handle common numeric types
switch (infoType) {
// 16-bit unsigned integers
case SQL_MAX_CONCURRENT_ACTIVITIES:
case SQL_MAX_DRIVER_CONNECTIONS:
case SQL_ODBC_API_CONFORMANCE:
case SQL_ODBC_SQL_CONFORMANCE:
{
SQLUSMALLINT value = *reinterpret_cast<SQLUSMALLINT*>(charBuffer);
return py::int_(value);
}

// 32-bit unsigned integers
case SQL_ASYNC_MODE:
case SQL_GETDATA_EXTENSIONS:
case SQL_MAX_ASYNC_CONCURRENT_STATEMENTS:
case SQL_MAX_COLUMNS_IN_GROUP_BY:
case SQL_MAX_COLUMNS_IN_ORDER_BY:
case SQL_MAX_COLUMNS_IN_SELECT:
case SQL_MAX_COLUMNS_IN_TABLE:
case SQL_MAX_ROW_SIZE:
case SQL_MAX_TABLES_IN_SELECT:
case SQL_MAX_USER_NAME_LEN:
case SQL_NUMERIC_FUNCTIONS:
case SQL_STRING_FUNCTIONS:
case SQL_SYSTEM_FUNCTIONS:
case SQL_TIMEDATE_FUNCTIONS:
{
SQLUINTEGER value = *reinterpret_cast<SQLUINTEGER*>(charBuffer);
return py::int_(value);
}

// Boolean flags (32-bit mask)
case SQL_AGGREGATE_FUNCTIONS:
case SQL_ALTER_TABLE:
case SQL_CATALOG_USAGE:
case SQL_DATETIME_LITERALS:
case SQL_INDEX_KEYWORDS:
case SQL_INSERT_STATEMENT:
case SQL_SCHEMA_USAGE:
case SQL_SQL_CONFORMANCE:
case SQL_SQL92_DATETIME_FUNCTIONS:
case SQL_SQL92_NUMERIC_VALUE_FUNCTIONS:
case SQL_SQL92_PREDICATES:
case SQL_SQL92_RELATIONAL_JOIN_OPERATORS:
case SQL_SQL92_STRING_FUNCTIONS:
case SQL_STATIC_CURSOR_ATTRIBUTES1:
case SQL_STATIC_CURSOR_ATTRIBUTES2:
{
SQLUINTEGER value = *reinterpret_cast<SQLUINTEGER*>(charBuffer);
return py::int_(value);
}

// Handle any other types as integers
default:
SQLUINTEGER value = *reinterpret_cast<SQLUINTEGER*>(charBuffer);
return py::int_(value);
}
}

// Default return in case nothing matched
return py::none();
}

py::object ConnectionHandle::getInfo(SQLUSMALLINT infoType) const {
if (!_conn) {
ThrowStdException("Connection object is not initialized");
}
return _conn->getInfo(infoType);
}
6 changes: 6 additions & 0 deletions mssql_python/pybind/connection/connection.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ class Connection {
// Allocate a new statement handle on this connection.
SqlHandlePtr allocStatementHandle();

// Get information about the driver and data source
py::object getInfo(SQLUSMALLINT infoType) const;

private:
void allocateDbcHandle();
void checkError(SQLRETURN ret) const;
Expand All @@ -67,6 +70,9 @@ class ConnectionHandle {
bool getAutocommit() const;
SqlHandlePtr allocStatementHandle();

// Get information about the driver and data source
py::object getInfo(SQLUSMALLINT infoType) const;

private:
std::shared_ptr<Connection> _conn;
bool _usePool;
Expand Down
7 changes: 5 additions & 2 deletions mssql_python/pybind/ddbc_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ SQLBindColFunc SQLBindCol_ptr = nullptr;
SQLDescribeColFunc SQLDescribeCol_ptr = nullptr;
SQLMoreResultsFunc SQLMoreResults_ptr = nullptr;
SQLColAttributeFunc SQLColAttribute_ptr = nullptr;
SQLGetInfoFunc SQLGetInfo_ptr = nullptr;

// Transaction APIs
SQLEndTranFunc SQLEndTran_ptr = nullptr;
Expand Down Expand Up @@ -779,6 +780,7 @@ DriverHandle LoadDriverOrThrowException() {
SQLDescribeCol_ptr = GetFunctionPointer<SQLDescribeColFunc>(handle, "SQLDescribeColW");
SQLMoreResults_ptr = GetFunctionPointer<SQLMoreResultsFunc>(handle, "SQLMoreResults");
SQLColAttribute_ptr = GetFunctionPointer<SQLColAttributeFunc>(handle, "SQLColAttributeW");
SQLGetInfo_ptr = GetFunctionPointer<SQLGetInfoFunc>(handle, "SQLGetInfoW");

SQLEndTran_ptr = GetFunctionPointer<SQLEndTranFunc>(handle, "SQLEndTran");
SQLDisconnect_ptr = GetFunctionPointer<SQLDisconnectFunc>(handle, "SQLDisconnect");
Expand All @@ -796,7 +798,7 @@ DriverHandle LoadDriverOrThrowException() {
SQLGetData_ptr && SQLNumResultCols_ptr && SQLBindCol_ptr &&
SQLDescribeCol_ptr && SQLMoreResults_ptr && SQLColAttribute_ptr &&
SQLEndTran_ptr && SQLDisconnect_ptr && SQLFreeHandle_ptr &&
SQLFreeStmt_ptr && SQLGetDiagRec_ptr;
SQLFreeStmt_ptr && SQLGetDiagRec_ptr && SQLGetInfo_ptr;

if (!success) {
ThrowStdException("Failed to load required function pointers from driver.");
Expand Down Expand Up @@ -2554,7 +2556,8 @@ PYBIND11_MODULE(ddbc_bindings, m) {
.def("rollback", &ConnectionHandle::rollback, "Rollback the current transaction")
.def("set_autocommit", &ConnectionHandle::setAutocommit)
.def("get_autocommit", &ConnectionHandle::getAutocommit)
.def("alloc_statement_handle", &ConnectionHandle::allocStatementHandle);
.def("alloc_statement_handle", &ConnectionHandle::allocStatementHandle)
.def("get_info", &ConnectionHandle::getInfo, py::arg("info_type"));
m.def("enable_pooling", &enable_pooling, "Enable global connection pooling");
m.def("close_pooling", []() {ConnectionPoolManager::getInstance().closePools();});
m.def("DDBCSQLExecDirect", &SQLExecDirect_wrap, "Execute a SQL query directly");
Expand Down
2 changes: 2 additions & 0 deletions mssql_python/pybind/ddbc_bindings.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ typedef SQLRETURN (SQL_API* SQLDescribeColFunc)(SQLHSTMT, SQLUSMALLINT, SQLWCHAR
typedef SQLRETURN (SQL_API* SQLMoreResultsFunc)(SQLHSTMT);
typedef SQLRETURN (SQL_API* SQLColAttributeFunc)(SQLHSTMT, SQLUSMALLINT, SQLUSMALLINT, SQLPOINTER,
SQLSMALLINT, SQLSMALLINT*, SQLPOINTER);
typedef SQLRETURN (SQL_API* SQLGetInfoFunc)(SQLHDBC, SQLUSMALLINT, SQLPOINTER, SQLSMALLINT, SQLSMALLINT*);

// Transaction APIs
typedef SQLRETURN (SQL_API* SQLEndTranFunc)(SQLSMALLINT, SQLHANDLE, SQLSMALLINT);
Expand Down Expand Up @@ -148,6 +149,7 @@ extern SQLBindColFunc SQLBindCol_ptr;
extern SQLDescribeColFunc SQLDescribeCol_ptr;
extern SQLMoreResultsFunc SQLMoreResults_ptr;
extern SQLColAttributeFunc SQLColAttribute_ptr;
extern SQLGetInfoFunc SQLGetInfo_ptr;

// Transaction APIs
extern SQLEndTranFunc SQLEndTran_ptr;
Expand Down
Loading