diff --git a/ext/pdo_odbc/odbc_driver.c b/ext/pdo_odbc/odbc_driver.c index dbdb0066578b..62cfd4ed583f 100644 --- a/ext/pdo_odbc/odbc_driver.c +++ b/ext/pdo_odbc/odbc_driver.c @@ -152,6 +152,7 @@ static bool odbc_handle_preparer(pdo_dbh_t *dbh, zend_string *sql, pdo_stmt_t *s S->H = H; S->assume_utf8 = H->assume_utf8; + S->stmt_timeout = H->stmt_timeout; /* before we prepare, we need to peek at the query; if it uses named parameters, * 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 } } + rc = SQLSetStmtAttr(S->stmt, SQL_ATTR_QUERY_TIMEOUT, (SQLPOINTER)(uintptr_t) S->stmt_timeout, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO) { + pdo_odbc_stmt_error("SQLSetStmtAttr: SQL_ATTR_QUERY_TIMEOUT"); + SQLFreeHandle(SQL_HANDLE_STMT, S->stmt); + if (nsql) { + zend_string_release(nsql); + } + return false; + } + rc = SQLPrepare(S->stmt, (SQLCHAR *) ZSTR_VAL(sql), SQL_NTS); if (nsql) { zend_string_release(nsql); @@ -341,6 +352,7 @@ static bool odbc_handle_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) { pdo_odbc_db_handle *H = (pdo_odbc_db_handle *)dbh->driver_data; bool bval; + zend_long lval; switch (attr) { 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) } } return true; + case PDO_ATTR_TIMEOUT: + if (!pdo_get_long_param(&lval, val)) { + return false; + } + /* This will only apply to the current connection and + * any new statements; currently no way to set timeout + * for already existing statements. + */ + H->stmt_timeout = H->conn_timeout = lval; + /* set odbc conn attr in case driver cares */ + RETCODE rc = SQLSetConnectAttr( + H->dbc, + SQL_ATTR_CONNECTION_TIMEOUT, + (SQLPOINTER) lval, + SQL_IS_INTEGER + ); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error( + dbh->auto_commit ? "SQLSetConnectAttr AUTOCOMMIT = ON" : "SQLSetConnectAttr AUTOCOMMIT = OFF" + ); + return false; + } + return true; default: strcpy(H->einfo.last_err_msg, "Unknown Attribute"); H->einfo.what = "setAttribute"; @@ -408,16 +443,17 @@ static int odbc_handle_get_attr(pdo_dbh_t *dbh, zend_long attr, zval *val) return pdo_odbc_get_info_string(dbh, SQL_DBMS_VER, val); case PDO_ATTR_SERVER_INFO: return pdo_odbc_get_info_string(dbh, SQL_DBMS_NAME, val); - case PDO_ATTR_PREFETCH: case PDO_ATTR_TIMEOUT: - case PDO_ATTR_CONNECTION_STATUS: - break; + ZVAL_LONG(val, H->conn_timeout); + return 1; case PDO_ODBC_ATTR_ASSUME_UTF8: ZVAL_BOOL(val, H->assume_utf8); return 1; case PDO_ATTR_AUTOCOMMIT: ZVAL_BOOL(val, dbh->auto_commit); return 1; + default: + break; } return 0; } @@ -475,7 +511,7 @@ static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ pdo_odbc_db_handle *H; RETCODE rc; int use_direct = 0; - zend_ulong cursor_lib; + zend_ulong cursor_lib, connection_timeout; H = pecalloc(1, sizeof(*H), dbh->is_persistent); @@ -525,6 +561,15 @@ static int pdo_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ goto fail; } + /* setup connection timeout */ + connection_timeout = pdo_attr_lval(driver_options, PDO_ATTR_TIMEOUT, 0); + rc = SQLSetConnectAttr(H->dbc, SQL_ATTR_CONNECTION_TIMEOUT, (void*)connection_timeout, SQL_IS_INTEGER); + if (rc != SQL_SUCCESS) { + pdo_odbc_drv_error("SQLSetConnectAttr SQL_ATTR_CONNECTION_TIMEOUT"); + goto fail; + } + H->stmt_timeout = H->conn_timeout = connection_timeout; + /* a connection string may have = but not ; - i.e. "DSN=PHP" */ if (strchr(dbh->data_source, '=')) { SQLCHAR dsnbuf[1024]; diff --git a/ext/pdo_odbc/php_pdo_odbc_int.h b/ext/pdo_odbc/php_pdo_odbc_int.h index 52d66abfba01..945dee40ea96 100644 --- a/ext/pdo_odbc/php_pdo_odbc_int.h +++ b/ext/pdo_odbc/php_pdo_odbc_int.h @@ -121,6 +121,8 @@ typedef struct { PDO_ODBC_HENV env; PDO_ODBC_HDBC dbc; pdo_odbc_errinfo einfo; + SQLUINTEGER conn_timeout; + SQLUINTEGER stmt_timeout; unsigned assume_utf8:1; unsigned _spare:31; } pdo_odbc_db_handle; @@ -144,6 +146,7 @@ typedef struct { char *convbuf; zend_ulong convbufsize; HashTable *out_buffers; + SQLUINTEGER stmt_timeout; unsigned going_long:1; unsigned assume_utf8:1; unsigned params_dirty:1; diff --git a/ext/pdo_odbc/tests/timeout.phpt b/ext/pdo_odbc/tests/timeout.phpt new file mode 100644 index 000000000000..b764b3ba2d55 --- /dev/null +++ b/ext/pdo_odbc/tests/timeout.phpt @@ -0,0 +1,49 @@ +--TEST-- +PDO_DBLIB: Set query timeouts +--EXTENSIONS-- +pdo_dblib +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_TIMEOUT, 1); +$stmt = $db->prepare($sql); +try { + $stmt->execute(); + echo "Shouldn't happen\n"; +} catch (PDOException) { + echo "OK for timeout set after\n"; +} + +// regular timeout attribute will affect query timeout, causing this query to fail +$attributes = [PDO::ATTR_TIMEOUT => 1]; +$db = new PDO($dsn, $user, $password, $attributes); +$stmt = $db->prepare($sql); +try { + $stmt->execute(); + echo "Shouldn't happen\n"; +} catch (PDOException) { + echo "OK for timeout set on ctor\n"; +} + +?> +--EXPECT-- +OK for timeout set after +OK for timeout set on ctor