diff --git a/setup.py b/setup.py index 7dcdba33..c753528e 100755 --- a/setup.py +++ b/setup.py @@ -116,6 +116,7 @@ def get_compiler_settings(): settings['libraries'].append('odbc32') settings['libraries'].append('advapi32') + settings['libraries'].append('user32') elif os.environ.get("OS", '').lower().startswith('windows'): # Windows Cygwin (posix on windows) diff --git a/src/connection.cpp b/src/connection.cpp index 1a50d2b5..975f521b 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -59,7 +59,7 @@ static char* StrDup(const char* text) { } -static bool Connect(PyObject* pConnectString, HDBC hdbc, long timeout, PyObject* encoding) +static bool Connect(PyObject* pConnectString, HDBC hdbc, long timeout, PyObject* encoding, SQLUSMALLINT driver_completion) { assert(PyUnicode_Check(pConnectString)); @@ -88,12 +88,31 @@ static bool Connect(PyObject* pConnectString, HDBC hdbc, long timeout, PyObject* if (!cstring.isValid()) return false; + SQLHWND hwnd = 0; +#ifdef _WIN32 + if (driver_completion != SQL_DRIVER_NOPROMPT) + { + hwnd = GetDesktopWindow(); + if (!hwnd) + { + PyErr_SetString(OperationalError, "Failed to get desktop window handle"); + return false; + } + } +#endif + Py_BEGIN_ALLOW_THREADS - ret = SQLDriverConnectW(hdbc, 0, cstring, SQL_NTS, 0, 0, 0, SQL_DRIVER_NOPROMPT); + ret = SQLDriverConnectW(hdbc, hwnd, cstring, SQL_NTS, 0, 0, 0, driver_completion); Py_END_ALLOW_THREADS if (SQL_SUCCEEDED(ret)) return true; + if (ret == SQL_NO_DATA) + { + PyErr_SetString(OperationalError, "User cancelled connection request"); + return false; + } + RaiseErrorFromHandle(0, "SQLDriverConnect", hdbc, SQL_NULL_HANDLE); return false; @@ -170,7 +189,7 @@ static bool ApplyPreconnAttrs(HDBC hdbc, SQLINTEGER ikey, PyObject *value, char } PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, long timeout, bool fReadOnly, - PyObject* attrs_before, PyObject* encoding) + PyObject* attrs_before, PyObject* encoding, SQLUSMALLINT driver_completion) { // // Allocate HDBC and connect @@ -213,7 +232,7 @@ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, long timeou } } - if (!Connect(pConnectString, hdbc, timeout, encoding)) + if (!Connect(pConnectString, hdbc, timeout, encoding, driver_completion)) { // Connect has already set an exception. Py_BEGIN_ALLOW_THREADS diff --git a/src/connection.h b/src/connection.h index c319c833..86ad35c7 100644 --- a/src/connection.h +++ b/src/connection.h @@ -104,7 +104,7 @@ struct Connection * exception is set and zero is returned. */ PyObject* Connection_New(PyObject* pConnectString, bool fAutoCommit, long timeout, bool fReadOnly, - PyObject* attrs_before, PyObject* encoding); + PyObject* attrs_before, PyObject* encoding, SQLUSMALLINT driver_completion); /* * Used by the Cursor to implement commit and rollback. diff --git a/src/pyodbc.pyi b/src/pyodbc.pyi index 685cebf1..e869158d 100644 --- a/src/pyodbc.pyi +++ b/src/pyodbc.pyi @@ -294,6 +294,11 @@ SQL_UNION: int SQL_USER_NAME: int SQL_XOPEN_CLI_YEAR: int +# driver connect completion modes +SQL_DRIVER_COMPLETE: int +SQL_DRIVER_COMPLETE_REQUIRED: int +SQL_DRIVER_NOPROMPT: int +SQL_DRIVER_PROMPT: int # pyodbc-specific constants BinaryNull: Any # to distinguish binary NULL values from char NULL values @@ -999,6 +1004,9 @@ def connect(connstring: str | None = None, readonly: To set the connection read-only. Not all drivers and/or databases support this. timeout: Set the connection timeout, in seconds. This is managed by the driver, not pyodbc, and not all drivers support this. + driver_completion: One of SQL_DRIVER_PROMPT (only available on Windows), + SQL_DRIVER_NO_PROMPT (the default), SQL_DRIVER_COMPLETE, or + SQL_DRIVER_COMPLETE_REQUIRED. attrs_before: Set low-level connection attributes before a connection is attempted. **kwargs: These key/value pairs are used to construct the connection string, or add to it (as "key=value;" combinations). diff --git a/src/pyodbcmodule.cpp b/src/pyodbcmodule.cpp index ede13fe3..93318c6f 100644 --- a/src/pyodbcmodule.cpp +++ b/src/pyodbcmodule.cpp @@ -400,6 +400,7 @@ static PyObject* mod_connect(PyObject* self, PyObject* args, PyObject* kwargs) int fReadOnly = 0; long timeout = 0; PyObject* encoding = 0; + SQLUSMALLINT driver_completion = SQL_DRIVER_NOPROMPT; Object attrs_before; // Optional connect attrs set before connecting @@ -475,6 +476,31 @@ static PyObject* mod_connect(PyObject* self, PyObject* args, PyObject* kwargs) encoding = value; continue; } + if (PyUnicode_CompareWithASCIIString(key, "driver_completion") == 0) + { + driver_completion = (SQLUSMALLINT)PyLong_AsLong(value); + if (PyErr_Occurred()) + return 0; + switch (driver_completion) + { + case SQL_DRIVER_PROMPT: + case SQL_DRIVER_COMPLETE: + case SQL_DRIVER_COMPLETE_REQUIRED: + case SQL_DRIVER_NOPROMPT: + break; + default: + PyErr_SetString(ProgrammingError, "Invalid value for driver_completion"); + return 0; + } +#ifndef _WIN32 + if (driver_completion == SQL_DRIVER_PROMPT) + { + PyErr_SetString(NotSupportedError, "SQL_DRIVER_PROMPT not supported on this platform"); + return 0; + } +#endif + continue; + } // Map DB API recommended names to ODBC names (e.g. user --> uid). @@ -521,7 +547,8 @@ static PyObject* mod_connect(PyObject* self, PyObject* args, PyObject* kwargs) } return (PyObject*)Connection_New(pConnectString.Get(), fAutoCommit != 0, timeout, - fReadOnly != 0, attrs_before.Detach(), encoding); + fReadOnly != 0, attrs_before.Detach(), encoding, + driver_completion); } @@ -739,7 +766,11 @@ static char connect_doc[] = " timeout\n" " An integer login timeout in seconds, used to set the SQL_ATTR_LOGIN_TIMEOUT\n" " attribute of the connection. The default is 0 which means the database's\n" - " default timeout, if any, is used.\n"; + " default timeout, if any, is used.\n" + "\n" + " driver_completion\n" + " One of SQL_DRIVER_PROMPT (only available on Windows), SQL_DRIVER_NO_PROMPT\n" + " (the default), SQL_DRIVER_COMPLETE, or SQL_DRIVER_COMPLETE_REQUIRED.\n"; static char timefromticks_doc[] = "TimeFromTicks(ticks) --> datetime.time\n" @@ -1065,6 +1096,12 @@ static const ConstantDef aConstants[] = { MAKECONST(SQL_PACKET_SIZE), MAKECONST(SQL_ATTR_ANSI_APP), + // Driver connection completion modes + MAKECONST(SQL_DRIVER_COMPLETE), + MAKECONST(SQL_DRIVER_COMPLETE_REQUIRED), + MAKECONST(SQL_DRIVER_NOPROMPT), + MAKECONST(SQL_DRIVER_PROMPT), + // SQL_CONVERT_X MAKECONST(SQL_CONVERT_FUNCTIONS), MAKECONST(SQL_CONVERT_BIGINT),