diff --git a/ext/pdo/pdo.c b/ext/pdo/pdo.c index a213fa1c87271..eb4d903240a0c 100644 --- a/ext/pdo/pdo.c +++ b/ext/pdo/pdo.c @@ -263,7 +263,7 @@ PHP_MINIT_FUNCTION(pdo) zend_hash_init(&pdo_driver_hash, 0, NULL, NULL, 1); zend_hash_init(&pdo_driver_specific_ce_hash, 0, NULL, NULL, 1); - le_ppdo = zend_register_list_destructors_ex(NULL, php_pdo_pdbh_dtor, + le_ppdo = zend_register_list_destructors_ex(php_pdo_pdbh_request_dtor, php_pdo_pdbh_dtor, "PDO persistent database", module_number); pdo_exception_ce = register_class_PDOException(spl_ce_RuntimeException); diff --git a/ext/pdo/pdo_dbh.c b/ext/pdo/pdo_dbh.c index 1bdfcd935cfd1..5ee080677ab28 100644 --- a/ext/pdo/pdo_dbh.c +++ b/ext/pdo/pdo_dbh.c @@ -152,7 +152,7 @@ PDO_API void pdo_handle_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt) /* {{{ */ } ZVAL_UNDEF(&info); - if (dbh->methods->fetch_err) { + if (dbh->methods && dbh->methods->fetch_err) { zval *item; array_init(&info); @@ -301,6 +301,21 @@ static bool create_driver_specific_pdo_object(pdo_driver_t *driver, zend_class_e return true; } +/* Fetch the registered persistent PDO object for the given key */ +static pdo_dbh_t *pdo_list_entry_from_key(const char *hashkey, size_t len) +{ + pdo_dbh_t *pdbh = NULL; + zend_resource *le; + + if ((le = zend_hash_str_find_ptr(&EG(persistent_list), hashkey, len)) != NULL) { + if (le->type == php_pdo_list_entry()) { + pdbh = (pdo_dbh_t*)le->ptr; + } + } + + return pdbh; +} + PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zend_object *current_object, zend_class_entry *called_scope, zval *new_zval_object) { pdo_dbh_t *dbh = NULL; @@ -389,8 +404,8 @@ PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zen /* is this supposed to be a persistent connection ? */ if (options) { zend_string *hash_key = NULL; - zend_resource *le; pdo_dbh_t *pdbh = NULL; + pdo_dbh_t **pdbh_ref = NULL; zval *v; if ((v = zend_hash_index_find_deref(Z_ARRVAL_P(options), PDO_ATTR_PERSISTENT)) != NULL) { @@ -412,36 +427,35 @@ PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zen if (is_persistent) { /* let's see if we have one cached.... */ - if ((le = zend_hash_find_ptr(&EG(persistent_list), hash_key)) != NULL) { - if (le->type == php_pdo_list_entry()) { - pdbh = (pdo_dbh_t*)le->ptr; - - /* is the connection still alive ? */ - if (pdbh->methods->check_liveness && FAILURE == (pdbh->methods->check_liveness)(pdbh)) { - /* nope... need to kill it */ - pdbh->refcount--; - zend_list_close(le); - pdbh = NULL; - } + pdbh = pdo_list_entry_from_key(ZSTR_VAL(hash_key), ZSTR_LEN(hash_key)); + /* is the connection still alive ? */ + if (!pdbh || pdbh->is_closed || + (pdbh->methods->check_liveness && FAILURE == (pdbh->methods->check_liveness)(pdbh))) { + /* clean up prior dbh reference */ + if (pdbh && pdbh->persistent_resource) { + pdbh_ref = (pdo_dbh_t**)pdbh->persistent_resource->ptr; + /* clear dbh reference to forestall end-of-request actions in destructor */ + *pdbh_ref = NULL; + zend_list_delete(pdbh->persistent_resource); + pdbh->persistent_resource = NULL; } - } - - if (pdbh) { - call_factory = 0; - } else { /* need a brand new pdbh */ pdbh = pecalloc(1, sizeof(*pdbh), 1); - - pdbh->refcount = 1; + pdbh_ref = emalloc(sizeof(*pdbh_ref)); + *pdbh_ref = pdbh; + pdbh->persistent_resource = zend_register_resource(pdbh_ref, php_pdo_list_entry()); + if (!pdbh->persistent_resource) { + php_error_docref(NULL, E_ERROR, "Failed to register resource entry"); + } pdbh->is_persistent = 1; pdbh->persistent_id = pemalloc(ZSTR_LEN(hash_key) + 1, true); memcpy((char *)pdbh->persistent_id, ZSTR_VAL(hash_key), ZSTR_LEN(hash_key) + 1); pdbh->persistent_id_len = ZSTR_LEN(hash_key); pdbh->def_stmt_ce = dbh->def_stmt_ce; + } else { + /* found viable dbh persisted */ + call_factory = 0; } - } - - if (pdbh) { efree(dbh); pdo_dbh_object_t *pdo_obj; @@ -493,6 +507,8 @@ PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zen /* register in the persistent list etc. */ /* we should also need to replace the object store entry, since it was created with emalloc */ + /* if a resource is already registered, then it failed a liveness check + and will be replaced, prompting destruct. */ if ((zend_register_persistent_resource( (char*)dbh->persistent_id, dbh->persistent_id_len, dbh, php_pdo_list_entry())) == NULL) { php_error_docref(NULL, E_ERROR, "Failed to register persistent entry"); @@ -522,9 +538,6 @@ PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zen } /* the connection failed; things will tidy up in free_storage */ - if (is_persistent) { - dbh->refcount--; - } /* XXX raise exception */ zend_restore_error_handling(&zeh); @@ -608,6 +621,8 @@ PHP_METHOD(PDO, prepare) PDO_DBH_CLEAR_ERR(); + PDO_CLOSE_CHECK; + if (options && (value = zend_hash_index_find(Z_ARRVAL_P(options), PDO_ATTR_STATEMENT_CLASS)) != NULL) { if (Z_TYPE_P(value) != IS_ARRAY) { zend_type_error("PDO::ATTR_STATEMENT_CLASS value must be of type array, %s given", @@ -680,6 +695,9 @@ PHP_METHOD(PDO, prepare) static bool pdo_is_in_transaction(pdo_dbh_t *dbh) { + if (dbh->is_closed) { + return false; + } if (dbh->methods->in_transaction) { return dbh->methods->in_transaction(dbh); } @@ -695,6 +713,8 @@ PHP_METHOD(PDO, beginTransaction) PDO_CONSTRUCT_CHECK; + PDO_CLOSE_CHECK; + if (pdo_is_in_transaction(dbh)) { zend_throw_exception_ex(php_pdo_get_exception(), 0, "There is already an active transaction"); RETURN_THROWS(); @@ -725,6 +745,8 @@ PHP_METHOD(PDO, commit) PDO_CONSTRUCT_CHECK; + PDO_CLOSE_CHECK; + if (!pdo_is_in_transaction(dbh)) { zend_throw_exception_ex(php_pdo_get_exception(), 0, "There is no active transaction"); RETURN_THROWS(); @@ -749,6 +771,8 @@ PHP_METHOD(PDO, rollBack) PDO_CONSTRUCT_CHECK; + PDO_CLOSE_CHECK; + if (!pdo_is_in_transaction(dbh)) { zend_throw_exception_ex(php_pdo_get_exception(), 0, "There is no active transaction"); RETURN_THROWS(); @@ -773,10 +797,25 @@ PHP_METHOD(PDO, inTransaction) PDO_CONSTRUCT_CHECK; + PDO_CLOSE_CHECK; + RETURN_BOOL(pdo_is_in_transaction(dbh)); } /* }}} */ +/* {{{ Determine if connected */ +PHP_METHOD(PDO, isConnected) +{ + pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS); + + ZEND_PARSE_PARAMETERS_NONE(); + + PDO_CONSTRUCT_CHECK; + + RETURN_BOOL(!dbh->is_closed); +} +/* }}} */ + PDO_API bool pdo_get_long_param(zend_long *lval, const zval *value) { switch (Z_TYPE_P(value)) { @@ -947,6 +986,12 @@ static bool pdo_dbh_attribute_set(pdo_dbh_t *dbh, zend_long attr, zval *value, u default:; } + if (!dbh->methods) { + pdo_raise_impl_error(dbh, NULL, "IM001", + "driver attributes not initialized, possibly due to disconnect"); + return false; + } + if (!dbh->methods->set_attribute) { goto fail; } @@ -1034,6 +1079,12 @@ PHP_METHOD(PDO, getAttribute) break; } + if (!dbh->methods) { + pdo_raise_impl_error(dbh, NULL, "IM001", + "driver attributes not initialized, possibly due to disconnect"); + RETURN_FALSE; + } + if (!dbh->methods->get_attribute) { pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support getting attributes"); RETURN_FALSE; @@ -1074,6 +1125,8 @@ PHP_METHOD(PDO, exec) PDO_DBH_CLEAR_ERR(); PDO_CONSTRUCT_CHECK; + PDO_CLOSE_CHECK; + ret = dbh->methods->doer(dbh, statement); if (ret == -1) { PDO_HANDLE_DBH_ERR(); @@ -1100,6 +1153,8 @@ PHP_METHOD(PDO, lastInsertId) PDO_DBH_CLEAR_ERR(); + PDO_CLOSE_CHECK; + if (!dbh->methods->last_id) { pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support lastInsertId()"); RETURN_FALSE; @@ -1161,7 +1216,8 @@ PHP_METHOD(PDO, errorInfo) if(!strncmp(dbh->error_code, PDO_ERR_NONE, sizeof(PDO_ERR_NONE))) goto fill_array; } - if (dbh->methods->fetch_err) { + /* Driver-implemented error is not available once database is shutdown. */ + if (dbh->methods && dbh->methods->fetch_err) { dbh->methods->fetch_err(dbh, dbh->query_stmt, return_value); } @@ -1210,6 +1266,8 @@ PHP_METHOD(PDO, query) PDO_DBH_CLEAR_ERR(); + PDO_CLOSE_CHECK; + if (!pdo_stmt_instantiate(dbh, return_value, dbh->def_stmt_ce, &dbh->def_stmt_ctor_args)) { RETURN_THROWS(); } @@ -1280,6 +1338,9 @@ PHP_METHOD(PDO, quote) PDO_CONSTRUCT_CHECK; PDO_DBH_CLEAR_ERR(); + + PDO_CLOSE_CHECK; + if (!dbh->methods->quoter) { pdo_raise_impl_error(dbh, NULL, "IM001", "driver does not support quoting"); RETURN_FALSE; @@ -1525,28 +1586,48 @@ void pdo_dbh_init(int module_number) pdo_dbh_object_handlers.get_gc = dbh_get_gc; } -static void dbh_free(pdo_dbh_t *dbh, bool free_persistent) +/* Disconnect from the database and free associated driver. */ +static void dbh_shutdown(pdo_dbh_t *dbh) { - int i; - - if (dbh->query_stmt) { - OBJ_RELEASE(dbh->query_stmt_obj); - dbh->query_stmt_obj = NULL; - dbh->query_stmt = NULL; + if (dbh->methods) { + dbh->methods->closer(dbh); } - if (dbh->is_persistent) { + /* Do not permit reference to driver methods to remain past closer(), which + * is responsible for both disconnecting the db and free-ing allocations. + * Ideally, this would only disconnect the database, not free the handle. */ + dbh->methods = NULL; + dbh->is_closed = true; +} + +/* {{{ Disconnect from the database. */ +PHP_METHOD(PDO, disconnect) +{ + pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS); + + ZEND_PARSE_PARAMETERS_NONE(); + + PDO_DBH_CLEAR_ERR(); + PDO_CONSTRUCT_CHECK; + + dbh_shutdown(dbh); + + PDO_HANDLE_DBH_ERR(); + + RETURN_TRUE; +} +/* }}} */ + +/* Free the database when the last pdo object referencing it is freed + * or when it has been registered as a php resource, i.e. is persistent, + * and the resource is destructed, whichever comes last. */ +static void dbh_free(pdo_dbh_t *dbh) +{ + int i; + #if ZEND_DEBUG - ZEND_ASSERT(!free_persistent || (dbh->refcount == 1)); + ZEND_ASSERT(dbh->refcount == 0); #endif - if (!free_persistent && (--dbh->refcount)) { - return; - } - } - - if (dbh->methods) { - dbh->methods->closer(dbh); - } if (dbh->data_source) { pefree((char *)dbh->data_source, dbh->is_persistent); @@ -1562,6 +1643,12 @@ static void dbh_free(pdo_dbh_t *dbh, bool free_persistent) pefree((char *)dbh->persistent_id, dbh->is_persistent); } + if (dbh->persistent_resource) { + pdo_dbh_t **dbh_ref = (pdo_dbh_t**)dbh->persistent_resource->ptr; + dbh->persistent_resource = NULL; + *dbh_ref = NULL; + } + if (!Z_ISUNDEF(dbh->def_stmt_ctor_args)) { zval_ptr_dtor(&dbh->def_stmt_ctor_args); } @@ -1576,25 +1663,45 @@ static void dbh_free(pdo_dbh_t *dbh, bool free_persistent) pefree(dbh, dbh->is_persistent); } +/* Whether the given database handler is presently registered as a resource. */ +static bool pdo_is_persisted(pdo_dbh_t *dbh) +{ + pdo_dbh_t *pdbh = NULL; + + if (dbh->persistent_id != NULL) { + pdbh = pdo_list_entry_from_key(dbh->persistent_id, dbh->persistent_id_len); + return dbh == pdbh; + } + + return false; +} + static void pdo_dbh_free_storage(zend_object *std) { pdo_dbh_t *dbh = php_pdo_dbh_fetch_inner(std); /* dbh might be null if we OOMed during object initialization. */ - if (!dbh) { - return; - } + if (dbh) { + /* stmt is not persistent, even if dbh is, so it must be freed with pdo. + * Consider copying stmt error code to dbh at this point, seemingly the reason + * that the stmt is even being held, or even better, to do that at the time of + * error and remove the reference altogether. */ + if (dbh->query_stmt) { + OBJ_RELEASE(dbh->query_stmt_obj); + dbh->query_stmt_obj = NULL; + dbh->query_stmt = NULL; + } - if (dbh->driver_data && dbh->methods && dbh->methods->rollback && pdo_is_in_transaction(dbh)) { - dbh->methods->rollback(dbh); - dbh->in_txn = false; + if (!(--dbh->refcount)) { + /* a persisted dbh will be freed when the resource is destructed. */ + if (!pdo_is_persisted(dbh)) { + dbh_shutdown(dbh); + dbh_free(dbh); + } + } } - if (dbh->is_persistent && dbh->methods && dbh->methods->persistent_shutdown) { - dbh->methods->persistent_shutdown(dbh); - } zend_object_std_dtor(std); - dbh_free(dbh, 0); } zend_object *pdo_dbh_new(zend_class_entry *ce) @@ -1608,17 +1715,43 @@ zend_object *pdo_dbh_new(zend_class_entry *ce) zend_std_get_properties_ex(&dbh->std); dbh->inner = ecalloc(1, sizeof(pdo_dbh_t)); dbh->inner->def_stmt_ce = pdo_dbstmt_ce; + dbh->inner->refcount++; return &dbh->std; } /* }}} */ +ZEND_RSRC_DTOR_FUNC(php_pdo_pdbh_request_dtor) /* {{{ */ +{ + if (res->ptr) { + pdo_dbh_t **dbh_ref = (pdo_dbh_t**)res->ptr; + if (*dbh_ref) { + pdo_dbh_t *dbh = (pdo_dbh_t*)*dbh_ref; + if (dbh->methods && dbh->methods->rollback && pdo_is_in_transaction(dbh)) { + dbh->methods->rollback(dbh); + dbh->in_txn = false; + } + if (dbh->methods && dbh->methods->persistent_shutdown) { + dbh->methods->persistent_shutdown(dbh); + } + dbh->persistent_resource = NULL; + } + efree(dbh_ref); + res->ptr = NULL; + } +} +/* }}} */ + ZEND_RSRC_DTOR_FUNC(php_pdo_pdbh_dtor) /* {{{ */ { if (res->ptr) { pdo_dbh_t *dbh = (pdo_dbh_t*)res->ptr; - dbh_free(dbh, 1); + if (!dbh->refcount) { + /* do not free if still referenced by pdo */ + dbh_shutdown(dbh); + dbh_free(dbh); + } res->ptr = NULL; } } diff --git a/ext/pdo/pdo_dbh.stub.php b/ext/pdo/pdo_dbh.stub.php index 7fcec0226b0ba..176cdd974f935 100644 --- a/ext/pdo/pdo_dbh.stub.php +++ b/ext/pdo/pdo_dbh.stub.php @@ -179,6 +179,8 @@ public function beginTransaction(): bool {} /** @tentative-return-type */ public function commit(): bool {} + public function disconnect(): bool {} + /** @tentative-return-type */ public function errorCode(): ?string {} @@ -197,6 +199,8 @@ public static function getAvailableDrivers(): array {} /** @tentative-return-type */ public function inTransaction(): bool {} + public function isConnected(): bool {} + /** @tentative-return-type */ public function lastInsertId(?string $name = null): string|false {} diff --git a/ext/pdo/pdo_dbh_arginfo.h b/ext/pdo/pdo_dbh_arginfo.h index 71df4c519e1a7..84c866e721579 100644 --- a/ext/pdo/pdo_dbh_arginfo.h +++ b/ext/pdo/pdo_dbh_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 006be61b2c519e7d9ca997a7f12135eb3e0f3500 */ + * Stub hash: 0c97cbaf080b205e34da368c3d1e80ac1a203324 */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_PDO___construct, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, dsn, IS_STRING, 0) @@ -20,6 +20,9 @@ ZEND_END_ARG_INFO() #define arginfo_class_PDO_commit arginfo_class_PDO_beginTransaction +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_PDO_disconnect, 0, 0, _IS_BOOL, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_errorCode, 0, 0, IS_STRING, 1) ZEND_END_ARG_INFO() @@ -38,6 +41,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_PDO_inTransaction arginfo_class_PDO_beginTransaction +#define arginfo_class_PDO_isConnected arginfo_class_PDO_disconnect + ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_PDO_lastInsertId, 0, 0, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, name, IS_STRING, 1, "null") ZEND_END_ARG_INFO() @@ -69,12 +74,14 @@ ZEND_METHOD(PDO, __construct); ZEND_METHOD(PDO, connect); ZEND_METHOD(PDO, beginTransaction); ZEND_METHOD(PDO, commit); +ZEND_METHOD(PDO, disconnect); ZEND_METHOD(PDO, errorCode); ZEND_METHOD(PDO, errorInfo); ZEND_METHOD(PDO, exec); ZEND_METHOD(PDO, getAttribute); ZEND_METHOD(PDO, getAvailableDrivers); ZEND_METHOD(PDO, inTransaction); +ZEND_METHOD(PDO, isConnected); ZEND_METHOD(PDO, lastInsertId); ZEND_METHOD(PDO, prepare); ZEND_METHOD(PDO, query); @@ -87,12 +94,14 @@ static const zend_function_entry class_PDO_methods[] = { ZEND_ME(PDO, connect, arginfo_class_PDO_connect, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(PDO, beginTransaction, arginfo_class_PDO_beginTransaction, ZEND_ACC_PUBLIC) ZEND_ME(PDO, commit, arginfo_class_PDO_commit, ZEND_ACC_PUBLIC) + ZEND_ME(PDO, disconnect, arginfo_class_PDO_disconnect, ZEND_ACC_PUBLIC) ZEND_ME(PDO, errorCode, arginfo_class_PDO_errorCode, ZEND_ACC_PUBLIC) ZEND_ME(PDO, errorInfo, arginfo_class_PDO_errorInfo, ZEND_ACC_PUBLIC) ZEND_ME(PDO, exec, arginfo_class_PDO_exec, ZEND_ACC_PUBLIC) ZEND_ME(PDO, getAttribute, arginfo_class_PDO_getAttribute, ZEND_ACC_PUBLIC) ZEND_ME(PDO, getAvailableDrivers, arginfo_class_PDO_getAvailableDrivers, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(PDO, inTransaction, arginfo_class_PDO_inTransaction, ZEND_ACC_PUBLIC) + ZEND_ME(PDO, isConnected, arginfo_class_PDO_isConnected, ZEND_ACC_PUBLIC) ZEND_ME(PDO, lastInsertId, arginfo_class_PDO_lastInsertId, ZEND_ACC_PUBLIC) ZEND_ME(PDO, prepare, arginfo_class_PDO_prepare, ZEND_ACC_PUBLIC) ZEND_ME(PDO, query, arginfo_class_PDO_query, ZEND_ACC_PUBLIC) diff --git a/ext/pdo/php_pdo.h b/ext/pdo/php_pdo.h index 59789a04c73f2..baefefc08d6a7 100644 --- a/ext/pdo/php_pdo.h +++ b/ext/pdo/php_pdo.h @@ -116,5 +116,14 @@ static inline void pdo_declare_deprecated_class_constant_long( goto cleanup; \ } \ +#define PDO_CLOSE_CHECK \ + if (dbh->is_closed) { \ + pdo_raise_impl_error(dbh, NULL, "01002", NULL); \ + if (dbh->error_mode == PDO_ERRMODE_EXCEPTION) { \ + RETURN_THROWS(); \ + } else { \ + RETURN_FALSE; \ + } \ + } \ #endif /* PHP_PDO_H */ diff --git a/ext/pdo/php_pdo_driver.h b/ext/pdo/php_pdo_driver.h index 9c5986ff8bce8..6ccf6b7c8319a 100644 --- a/ext/pdo/php_pdo_driver.h +++ b/ext/pdo/php_pdo_driver.h @@ -488,7 +488,9 @@ struct _pdo_dbh_t { /* persistent hash key associated with this handle */ const char *persistent_id; size_t persistent_id_len; - + /* a regular resource to prompt end-of-request actions */ + zend_resource *persistent_resource; + /* counter of _pdo_dbh_object_t referencing this handle */ uint32_t refcount; /* driver specific "class" methods for the dbh and stmt */ diff --git a/ext/pdo/php_pdo_int.h b/ext/pdo/php_pdo_int.h index e8befe9f819a3..fc0da5272b6f8 100644 --- a/ext/pdo/php_pdo_int.h +++ b/ext/pdo/php_pdo_int.h @@ -30,6 +30,7 @@ void pdo_dbh_init(int module_number); void pdo_stmt_init(void); extern const zend_function_entry pdo_dbh_functions[]; +extern ZEND_RSRC_DTOR_FUNC(php_pdo_pdbh_request_dtor); extern ZEND_RSRC_DTOR_FUNC(php_pdo_pdbh_dtor); extern zend_object *pdo_dbstmt_new(zend_class_entry *ce); diff --git a/ext/pdo/tests/bug_63343.phpt b/ext/pdo/tests/bug_63343.phpt new file mode 100644 index 0000000000000..9775c4d000715 --- /dev/null +++ b/ext/pdo/tests/bug_63343.phpt @@ -0,0 +1,47 @@ +--TEST-- +PDO Common: Bug #63343 (Commit failure for repeated persistent connection) +--EXTENSIONS-- +pdo +--SKIPIF-- +beginTransaction(); +} catch (PDOException $exception) { + die("skip not relevant for driver - does not permit transactions"); +} +?> +--FILE-- + true))); + +$db1 = PDOTest::factory('PDO', false); +$db1->beginTransaction(); +var_dump($db1->inTransaction()); +/* db2 should assume persistent conn, including txn state */ +$db2 = PDOTest::factory('PDO', false); +var_dump($db2->inTransaction()); +/* destructing db1 should not rollback db2 */ +$db1 = null; +var_dump($db2->inTransaction()); +$db2 = null; +/* db3 should assume persistent conn, despite no remaining PDOs */ +$db3 = PDOTest::factory('PDO', false); +var_dump($db3->inTransaction()); +var_dump($db3->commit()); + +?> +--EXPECTF-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/ext/pdo/tests/pdo_040.phpt b/ext/pdo/tests/pdo_040.phpt new file mode 100644 index 0000000000000..a469d1c04dcf6 --- /dev/null +++ b/ext/pdo/tests/pdo_040.phpt @@ -0,0 +1,105 @@ +--TEST-- +PDO Common: Explicit disconnect of common and persistent PDO +--EXTENSIONS-- +pdo +--SKIPIF-- +beginTransaction(); +} catch (PDOException $exception) { + die("skip not relevant for driver - does not permit transactions"); +} +?> +--FILE-- +isConnected()); +$db2 = PDOTest::factory('PDO', false); +var_dump($db2->isConnected()); +putenv("PDOTEST_ATTR=" . serialize(array(PDO::ATTR_PERSISTENT => true))); +$pdb3 = PDOTest::factory('PDO', false); +var_dump($pdb3->isConnected()); +$pdb4 = PDOTest::factory('PDO', false); +$pdb4->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +var_dump($pdb4->isConnected()); + +/* disconnect regular PDO, confirm other PDOs still connected */ +var_dump($db1->disconnect()); +var_dump($db1->isConnected()); +var_dump($db1->beginTransaction()); +var_dump($db1->errorCode()); +var_dump($db1->errorInfo()); +var_dump($db2->isConnected()); +var_dump($pdb3->isConnected()); + +/* disconnect persistent PDO, confirm other persistent PDO disconnected */ +var_dump($pdb3->disconnect()); +var_dump($pdb3->isConnected()); +var_dump($pdb4->isConnected()); +var_dump($pdb4->disconnect()); + +/* new persistent PDO should prompt new connection */ +$pdb5 = PDOTest::factory('PDO', false); +var_dump($pdb5->isConnected()); +var_dump($pdb5->inTransaction()); +var_dump($pdb5->beginTransaction()); + +/* new persistent connection should not be inherited */ +var_dump($pdb4->isConnected()); +try { + $pdb4->beginTransaction(); +} catch (PDOException $e) { + var_dump($e->getMessage()); +} + +$db1 = null; +$db2 = null; /* trigger shutdown without explicit disconnect */ +$pdb3 = null; +$pdb4 = null; +$pdb5 = null; /* destruct should not disconnect */ + +/* no PDOs remain, but persistent connection should remain */ +$pdb6 = PDOTest::factory('PDO', false); +var_dump($pdb6->inTransaction()); + +?> +--EXPECTF-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) + +Warning: PDO::beginTransaction(): SQLSTATE[01002]: Disconnect error in %s on line %d +bool(false) +string(5) "01002" +array(3) { + [0]=> + string(5) "01002" + [1]=> + NULL + [2]=> + NULL +} +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(true) +bool(true) +bool(false) +bool(true) +bool(false) +string(33) "SQLSTATE[01002]: Disconnect error" +bool(true) diff --git a/ext/pdo_mysql/tests/pdo_mysql_interface.phpt b/ext/pdo_mysql/tests/pdo_mysql_interface.phpt index 5d7fd83c2d8e7..c22e4bc331371 100644 --- a/ext/pdo_mysql/tests/pdo_mysql_interface.phpt +++ b/ext/pdo_mysql/tests/pdo_mysql_interface.phpt @@ -29,6 +29,8 @@ MySQLPDOTest::skipNotTransactionalEngine(); 'quote' => true, 'inTransaction' => true, 'getAvailableDrivers' => true, + 'disconnect' => true, + 'isConnected' => true, ]; $classname = get_class($db);