Skip to content

Commit 9002ef5

Browse files
committed
ext/pdo_odbc: timeout support
ODBC has both a connection timeout for the connection level and a query timeout at the statement level. Annoyingly, these are somewhat poorly defined. The connection timeout, for instance, can be defined as either a login/connection setup timeout, or as a generic "any action" timeout, and if the latter, the query timeout may not necessarily be implemented. Accordingly, because we have no way to differentiate, we have to set both, and have special handling for the initial connection, as the setAttribute call won't handle early enough if it only handles initial connection timeouts. This means changing it after for a connection isn't relevant for existing statements that have already been constructed; this commit doesn't add a way to change it for a statement after changing it on the connection (be it via setAttribute on the statement, or somehow changing statements from the connection setAttribute). Also annoying is the difference in behaviour between drivers when a timeout happens. For example, SQL Server has a query timeout which just gracefully ends the current query, but Db2i only has a connection timeout which puts the whole connection into an invalid state (but at least it should fail a liveness check for a persistent connection). We don't have much control over this unfortunately. In the future, we should consider clarifying the PDO timeout attribute behaviour's on statements vs. connections, or make them separate attributes, like how PDO_DBLIB has.
1 parent 147c8ad commit 9002ef5

2 files changed

Lines changed: 51 additions & 1 deletion

File tree

‎ext/pdo_odbc/odbc_driver.c‎

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ static bool odbc_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *s
152152

153153
S->H = H;
154154
S->assume_utf8 = H->assume_utf8;
155+
S->stmt_timeout = H->stmt_timeout;
155156

156157
/* before we prepare, we need to peek at the query; if it uses named parameters,
157158
* we want PDO to rewrite them for us */
@@ -194,6 +195,16 @@ static bool odbc_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *s
194195
}
195196
}
196197

198+
rc = SQLSetStmtAttr(S->stmt, SQL_ATTR_QUERY_TIMEOUT, (SQLPOINTER)S->stmt_timeout, SQL_IS_INTEGER);
199+
if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) {
200+
pdo_odbc_stmt_error("SQLSetStmtAttr: SQL_ATTR_QUERY_TIMEOUT");
201+
SQLFreeHandle(SQL_HANDLE_STMT, S->stmt);
202+
if (nsql) {
203+
zend_string_release(nsql);
204+
}
205+
return false;
206+
}
207+
197208
rc = SQLPrepare(S->stmt, (SQLCHAR *) ZSTR_VAL(sql), SQL_NTS);
198209
if (nsql) {
199210
zend_string_release(nsql);
@@ -341,6 +352,7 @@ static bool odbc_handle_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val)
341352
{
342353
pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data;
343354
bool bval;
355+
zend_long lval;
344356

345357
switch (attr) {
346358
case PDO_ODBC_ATTR_ASSUME_UTF8:
@@ -373,6 +385,29 @@ static bool odbc_handle_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val)
373385
}
374386
}
375387
return true;
388+
case PDO_ATTR_TIMEOUT:
389+
if (!pdo_get_long_param(&lval, val)) {
390+
return false;
391+
}
392+
/* This will only apply to the current connection and
393+
* any new statements; currently no way to set timeout
394+
* for already existing statements.
395+
*/
396+
H->stmt_timeout = H->conn_timeout = lval;
397+
/* set odbc conn attr in case driver cares */
398+
RETCODE rc = SQLSetConnectAttr(
399+
H->dbc,
400+
SQL_ATTR_CONNECTION_TIMEOUT,
401+
(SQLPOINTER) lval,
402+
SQL_IS_INTEGER
403+
);
404+
if (rc != SQL_SUCCESS) {
405+
pdo_odbc_drv_error(
406+
dbh->auto_commit ? "SQLSetConnectAttr AUTOCOMMIT = ON" : "SQLSetConnectAttr AUTOCOMMIT = OFF"
407+
);
408+
return false;
409+
}
410+
return true;
376411
default:
377412
strcpy(H->einfo.last_err_msg, "Unknown Attribute");
378413
H->einfo.what = "setAttribute";
@@ -408,6 +443,9 @@ static int odbc_handle_get_attr(pdo_dbh_t *dbh, zend_long attr, zval *val)
408443
return pdo_odbc_get_info_string(dbh, SQL_DBMS_VER, val);
409444
case PDO_ATTR_SERVER_INFO:
410445
return pdo_odbc_get_info_string(dbh, SQL_DBMS_NAME, val);
446+
case PDO_ATTR_TIMEOUT:
447+
ZVAL_LONG(val, H->conn_timeout);
448+
return 1;
411449
case PDO_ODBC_ATTR_ASSUME_UTF8:
412450
ZVAL_BOOL(val, H->assume_utf8);
413451
return 1;
@@ -473,7 +511,7 @@ static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{
473511
pdo_odbc_db_handle *H;
474512
RETCODE rc;
475513
int use_direct = 0;
476-
zend_ulong cursor_lib;
514+
zend_ulong cursor_lib, connection_timeout;
477515

478516
H = pecalloc(1, sizeof(*H), dbh->is_persistent);
479517

@@ -523,6 +561,15 @@ static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{
523561
goto fail;
524562
}
525563

564+
/* setup connection timeout */
565+
connection_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 0);
566+
rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_CONNECTION_TIMEOUT, (void*)connection_timeout, SQL_IS_INTEGER);
567+
if (rc != SQL_SUCCESS) {
568+
pdo_odbc_drv_error("SQLSetConnectAttr SQL_ATTR_CONNECTION_TIMEOUT");
569+
goto fail;
570+
}
571+
H->stmt_timeout = H->conn_timeout = connection_timeout;
572+
526573
/* a connection string may have = but not ; - i.e. "DSN=PHP" */
527574
if (strchr(dbh->data_source, '=')) {
528575
SQLCHAR dsnbuf[1024];

‎ext/pdo_odbc/php_pdo_odbc_int.h‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ typedef struct {
121121
PDO_ODBC_HENV env;
122122
PDO_ODBC_HDBC dbc;
123123
pdo_odbc_errinfo einfo;
124+
SQLUINTEGER conn_timeout;
125+
SQLUINTEGER stmt_timeout;
124126
unsigned assume_utf8:1;
125127
unsigned _spare:31;
126128
} pdo_odbc_db_handle;
@@ -144,6 +146,7 @@ typedef struct {
144146
char *convbuf;
145147
zend_ulong convbufsize;
146148
HashTable *out_buffers;
149+
SQLUINTEGER stmt_timeout;
147150
unsigned going_long:1;
148151
unsigned assume_utf8:1;
149152
unsigned params_dirty:1;

0 commit comments

Comments
 (0)