@@ -221,6 +221,21 @@ static char *dsn_from_uri(char *uri, char *buf, size_t buflen) /* {{{ */
221221}
222222/* }}} */
223223
224+ /* Fetch the registered persistent PDO object for the given key */
225+ static pdo_dbh_t * pdo_list_entry_from_key (const char * hashkey , size_t len )
226+ {
227+ pdo_dbh_t * pdbh = NULL ;
228+ zend_resource * le ;
229+
230+ if ((le = zend_hash_str_find_ptr (& EG (persistent_list ), hashkey , len )) != NULL ) {
231+ if (le -> type == php_pdo_list_entry ()) {
232+ pdbh = (pdo_dbh_t * )le -> ptr ;
233+ }
234+ }
235+
236+ return pdbh ;
237+ }
238+
224239/* {{{ */
225240PHP_METHOD (PDO , __construct )
226241{
@@ -297,7 +312,6 @@ PHP_METHOD(PDO, __construct)
297312 if (options ) {
298313 int plen = 0 ;
299314 char * hashkey = NULL ;
300- zend_resource * le ;
301315 pdo_dbh_t * pdbh = NULL ;
302316 zval * v ;
303317
@@ -320,36 +334,22 @@ PHP_METHOD(PDO, __construct)
320334
321335 if (is_persistent ) {
322336 /* let's see if we have one cached.... */
323- if ((le = zend_hash_str_find_ptr (& EG (persistent_list ), hashkey , plen )) != NULL ) {
324- if (le -> type == php_pdo_list_entry ()) {
325- pdbh = (pdo_dbh_t * )le -> ptr ;
326-
327- /* is the connection still alive ? */
328- if (pdbh -> methods -> check_liveness && FAILURE == (pdbh -> methods -> check_liveness )(pdbh )) {
329- /* nope... need to kill it */
330- pdbh -> refcount -- ;
331- zend_list_close (le );
332- pdbh = NULL ;
333- }
334- }
335- }
336-
337- if (pdbh ) {
338- call_factory = 0 ;
339- } else {
337+ pdbh = pdo_list_entry_from_key (hashkey , plen );
338+ /* is the connection still alive ? */
339+ if (!pdbh || pdbh -> is_closed ||
340+ (pdbh -> methods -> check_liveness && FAILURE == (pdbh -> methods -> check_liveness )(pdbh ))) {
340341 /* need a brand new pdbh */
341342 pdbh = pecalloc (1 , sizeof (* pdbh ), 1 );
342343
343- pdbh -> refcount = 1 ;
344344 pdbh -> is_persistent = 1 ;
345345 pdbh -> persistent_id = pemalloc (plen + 1 , 1 );
346346 memcpy ((char * )pdbh -> persistent_id , hashkey , plen + 1 );
347347 pdbh -> persistent_id_len = plen ;
348348 pdbh -> def_stmt_ce = dbh -> def_stmt_ce ;
349+ } else {
350+ /* found viable dbh persisted */
351+ call_factory = 0 ;
349352 }
350- }
351-
352- if (pdbh ) {
353353 efree (dbh );
354354 /* switch over to the persistent one */
355355 Z_PDO_OBJECT_P (object )-> inner = pdbh ;
@@ -393,6 +393,8 @@ PHP_METHOD(PDO, __construct)
393393 /* register in the persistent list etc. */
394394 /* we should also need to replace the object store entry,
395395 since it was created with emalloc */
396+ /* if a resource is already registered, then it failed a liveness check
397+ * and will be replaced, prompting destruct. */
396398 if ((zend_register_persistent_resource (
397399 (char * )dbh -> persistent_id , dbh -> persistent_id_len , dbh , php_pdo_list_entry ())) == NULL ) {
398400 php_error_docref (NULL , E_ERROR , "Failed to register persistent entry" );
@@ -422,9 +424,6 @@ PHP_METHOD(PDO, __construct)
422424 }
423425
424426 /* the connection failed; things will tidy up in free_storage */
425- if (is_persistent ) {
426- dbh -> refcount -- ;
427- }
428427
429428 /* XXX raise exception */
430429 zend_restore_error_handling (& zeh );
@@ -587,6 +586,9 @@ PHP_METHOD(PDO, prepare)
587586
588587
589588static bool pdo_is_in_transaction (pdo_dbh_t * dbh ) {
589+ if (dbh -> is_closed ) {
590+ return false;
591+ }
590592 if (dbh -> methods -> in_transaction ) {
591593 return dbh -> methods -> in_transaction (dbh );
592594 }
@@ -684,6 +686,17 @@ PHP_METHOD(PDO, inTransaction)
684686}
685687/* }}} */
686688
689+ /* {{{ Determine if connected */
690+ PHP_METHOD (PDO , isConnected )
691+ {
692+ pdo_dbh_t * dbh = Z_PDO_DBH_P (ZEND_THIS );
693+
694+ ZEND_PARSE_PARAMETERS_NONE ();
695+
696+ RETURN_BOOL (!dbh -> is_closed );
697+ }
698+ /* }}} */
699+
687700PDO_API bool pdo_get_long_param (zend_long * lval , zval * value )
688701{
689702 switch (Z_TYPE_P (value )) {
@@ -1027,8 +1040,6 @@ PHP_METHOD(PDO, errorCode)
10271040
10281041 ZEND_PARSE_PARAMETERS_NONE ();
10291042
1030- PDO_CONSTRUCT_CHECK ;
1031-
10321043 if (dbh -> query_stmt ) {
10331044 RETURN_STRING (dbh -> query_stmt -> error_code );
10341045 }
@@ -1056,8 +1067,6 @@ PHP_METHOD(PDO, errorInfo)
10561067
10571068 ZEND_PARSE_PARAMETERS_NONE ();
10581069
1059- PDO_CONSTRUCT_CHECK ;
1060-
10611070 array_init (return_value );
10621071
10631072 if (dbh -> query_stmt ) {
@@ -1068,7 +1077,8 @@ PHP_METHOD(PDO, errorInfo)
10681077 if (!strncmp (dbh -> error_code , PDO_ERR_NONE , sizeof (PDO_ERR_NONE ))) goto fill_array ;
10691078 }
10701079
1071- if (dbh -> methods -> fetch_err ) {
1080+ /* Driver-implemented error is not available once database is shutdown. */
1081+ if (dbh -> driver && dbh -> methods -> fetch_err ) {
10721082 dbh -> methods -> fetch_err (dbh , dbh -> query_stmt , return_value );
10731083 }
10741084
@@ -1366,26 +1376,55 @@ void pdo_dbh_init(int module_number)
13661376 pdo_dbh_object_handlers .get_gc = dbh_get_gc ;
13671377}
13681378
1369- static void dbh_free (pdo_dbh_t * dbh , bool free_persistent )
1379+ /* Disconnect from the database and free associated driver. */
1380+ static void dbh_shutdown (pdo_dbh_t * dbh )
13701381{
1371- int i ;
1382+ if (dbh -> driver_data && dbh -> methods && dbh -> methods -> rollback && pdo_is_in_transaction (dbh )) {
1383+ dbh -> methods -> rollback (dbh );
1384+ dbh -> in_txn = false;
1385+ }
13721386
1373- if (dbh -> query_stmt ) {
1374- zval_ptr_dtor (& dbh -> query_stmt_zval );
1375- dbh -> query_stmt = NULL ;
1387+ if (dbh -> methods ) {
1388+ dbh -> methods -> closer (dbh );
13761389 }
13771390
1378- if (dbh -> is_persistent ) {
1391+ /* Do not permit reference to driver to remain past closer(), which
1392+ * is responsible for both disconnecting the db and free-ing allocations. */
1393+ dbh -> driver = NULL ;
1394+ dbh -> is_closed = true;
1395+ }
1396+
1397+ /* {{{ Disconnect from the database. */
1398+ PHP_METHOD (PDO , disconnect )
1399+ {
1400+ pdo_dbh_t * dbh = Z_PDO_DBH_P (ZEND_THIS );
1401+
1402+ ZEND_PARSE_PARAMETERS_NONE ();
1403+
1404+ PDO_DBH_CLEAR_ERR ();
1405+ PDO_CONSTRUCT_CHECK ;
1406+
1407+ dbh_shutdown (dbh );
1408+
1409+ PDO_HANDLE_DBH_ERR ();
1410+
1411+ RETURN_TRUE ;
1412+ }
1413+ /* }}} */
1414+
1415+ /* Free the database when the last pdo object referencing it is freed
1416+ * or when it has been registered as a php resource, i.e. is persistent,
1417+ * and the resource is destructed, whichever comes last. */
1418+ static void dbh_free (pdo_dbh_t * dbh )
1419+ {
1420+ int i ;
1421+
13791422#if ZEND_DEBUG
1380- ZEND_ASSERT (! free_persistent || ( dbh -> refcount == 1 ) );
1423+ ZEND_ASSERT (dbh -> refcount == 0 );
13811424#endif
1382- if (!free_persistent && (-- dbh -> refcount )) {
1383- return ;
1384- }
1385- }
13861425
1387- if (dbh -> methods ) {
1388- dbh -> methods -> closer (dbh );
1426+ if (dbh -> driver ) {
1427+ dbh_shutdown (dbh );
13891428 }
13901429
13911430 if (dbh -> data_source ) {
@@ -1416,25 +1455,45 @@ static void dbh_free(pdo_dbh_t *dbh, bool free_persistent)
14161455 pefree (dbh , dbh -> is_persistent );
14171456}
14181457
1458+ /* Whether the given database handler is presently registered as a resource. */
1459+ static bool pdo_is_persisted (pdo_dbh_t * dbh )
1460+ {
1461+ pdo_dbh_t * pdbh = NULL ;
1462+
1463+ if (dbh -> persistent_id != NULL ) {
1464+ pdbh = pdo_list_entry_from_key (dbh -> persistent_id , dbh -> persistent_id_len );
1465+ return dbh == pdbh ;
1466+ }
1467+
1468+ return false;
1469+ }
1470+
14191471static void pdo_dbh_free_storage (zend_object * std )
14201472{
14211473 pdo_dbh_t * dbh = php_pdo_dbh_fetch_inner (std );
14221474
14231475 /* dbh might be null if we OOMed during object initialization. */
1424- if (!dbh ) {
1425- return ;
1426- }
1476+ if (dbh ) {
1477+ if (dbh -> is_persistent && dbh -> methods && dbh -> methods -> persistent_shutdown ) {
1478+ dbh -> methods -> persistent_shutdown (dbh );
1479+ }
14271480
1428- if (dbh -> driver_data && dbh -> methods && dbh -> methods -> rollback && pdo_is_in_transaction (dbh )) {
1429- dbh -> methods -> rollback (dbh );
1430- dbh -> in_txn = false;
1431- }
1481+ /* stmt is not persistent, even if dbh is, so it must be freed with pdo.
1482+ * Consider copying stmt error code to dbh at this point, seemingly the reason
1483+ * that the stmt is even being held, or even better, to do that at the time of
1484+ * error and remove the reference altogether. */
1485+ if (dbh -> query_stmt ) {
1486+ zval_ptr_dtor (& dbh -> query_stmt_zval );
1487+ dbh -> query_stmt = NULL ;
1488+ }
14321489
1433- if (dbh -> is_persistent && dbh -> methods && dbh -> methods -> persistent_shutdown ) {
1434- dbh -> methods -> persistent_shutdown (dbh );
1490+ /* a persisted dbh will be freed when the resource is destructed. */
1491+ if (!(-- dbh -> refcount ) && !pdo_is_persisted (dbh )) {
1492+ dbh_free (dbh );
1493+ }
14351494 }
1495+
14361496 zend_object_std_dtor (std );
1437- dbh_free (dbh , 0 );
14381497}
14391498
14401499zend_object * pdo_dbh_new (zend_class_entry * ce )
@@ -1447,6 +1506,7 @@ zend_object *pdo_dbh_new(zend_class_entry *ce)
14471506 rebuild_object_properties (& dbh -> std );
14481507 dbh -> inner = ecalloc (1 , sizeof (pdo_dbh_t ));
14491508 dbh -> inner -> def_stmt_ce = pdo_dbstmt_ce ;
1509+ dbh -> inner -> refcount ++ ;
14501510
14511511 return & dbh -> std ;
14521512}
@@ -1457,7 +1517,10 @@ ZEND_RSRC_DTOR_FUNC(php_pdo_pdbh_dtor) /* {{{ */
14571517{
14581518 if (res -> ptr ) {
14591519 pdo_dbh_t * dbh = (pdo_dbh_t * )res -> ptr ;
1460- dbh_free (dbh , 1 );
1520+ if (!dbh -> refcount ) {
1521+ /* do not free if still referenced by pdo */
1522+ dbh_free (dbh );
1523+ }
14611524 res -> ptr = NULL ;
14621525 }
14631526}
0 commit comments