@@ -149,7 +149,7 @@ PDO_API void pdo_handle_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt) /* {{{ */
149149 }
150150
151151 ZVAL_UNDEF (& info );
152- if (dbh -> methods -> fetch_err ) {
152+ if (dbh -> methods && dbh -> methods -> fetch_err ) {
153153 zval * item ;
154154 array_init (& info );
155155
@@ -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 -> methods && dbh -> methods -> fetch_err ) {
10721082 dbh -> methods -> fetch_err (dbh , dbh -> query_stmt , return_value );
10731083 }
10741084
@@ -1366,23 +1376,53 @@ 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 methods to remain past closer(), which
1392+ * is responsible for both disconnecting the db and free-ing allocations.
1393+ * Ideally, this would only disconnect the database, not free the handle. */
1394+ dbh -> methods = NULL ;
1395+ dbh -> is_closed = true;
1396+ }
1397+
1398+ /* {{{ Disconnect from the database. */
1399+ PHP_METHOD (PDO , disconnect )
1400+ {
1401+ pdo_dbh_t * dbh = Z_PDO_DBH_P (ZEND_THIS );
1402+
1403+ ZEND_PARSE_PARAMETERS_NONE ();
1404+
1405+ PDO_DBH_CLEAR_ERR ();
1406+ PDO_CONSTRUCT_CHECK ;
1407+
1408+ dbh_shutdown (dbh );
1409+
1410+ PDO_HANDLE_DBH_ERR ();
1411+
1412+ RETURN_TRUE ;
1413+ }
1414+ /* }}} */
1415+
1416+ /* Free the database when the last pdo object referencing it is freed
1417+ * or when it has been registered as a php resource, i.e. is persistent,
1418+ * and the resource is destructed, whichever comes last. */
1419+ static void dbh_free (pdo_dbh_t * dbh )
1420+ {
1421+ int i ;
1422+
13791423#if ZEND_DEBUG
1380- ZEND_ASSERT (! free_persistent || ( dbh -> refcount == 1 ) );
1424+ ZEND_ASSERT (dbh -> refcount == 0 );
13811425#endif
1382- if (!free_persistent && (-- dbh -> refcount )) {
1383- return ;
1384- }
1385- }
13861426
13871427 if (dbh -> methods ) {
13881428 dbh -> methods -> closer (dbh );
@@ -1416,25 +1456,48 @@ static void dbh_free(pdo_dbh_t *dbh, bool free_persistent)
14161456 pefree (dbh , dbh -> is_persistent );
14171457}
14181458
1459+ /* Whether the given database handler is presently registered as a resource. */
1460+ static bool pdo_is_persisted (pdo_dbh_t * dbh )
1461+ {
1462+ pdo_dbh_t * pdbh = NULL ;
1463+
1464+ if (dbh -> persistent_id != NULL ) {
1465+ pdbh = pdo_list_entry_from_key (dbh -> persistent_id , dbh -> persistent_id_len );
1466+ return dbh == pdbh ;
1467+ }
1468+
1469+ return false;
1470+ }
1471+
14191472static void pdo_dbh_free_storage (zend_object * std )
14201473{
14211474 pdo_dbh_t * dbh = php_pdo_dbh_fetch_inner (std );
14221475
14231476 /* dbh might be null if we OOMed during object initialization. */
1424- if (!dbh ) {
1425- return ;
1426- }
1477+ if (dbh ) {
1478+ if (dbh -> is_persistent && dbh -> methods && dbh -> methods -> persistent_shutdown ) {
1479+ dbh -> methods -> persistent_shutdown (dbh );
1480+ }
14271481
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- }
1482+ /* stmt is not persistent, even if dbh is, so it must be freed with pdo.
1483+ * Consider copying stmt error code to dbh at this point, seemingly the reason
1484+ * that the stmt is even being held, or even better, to do that at the time of
1485+ * error and remove the reference altogether. */
1486+ if (dbh -> query_stmt ) {
1487+ zval_ptr_dtor (& dbh -> query_stmt_zval );
1488+ dbh -> query_stmt = NULL ;
1489+ }
14321490
1433- if (dbh -> is_persistent && dbh -> methods && dbh -> methods -> persistent_shutdown ) {
1434- dbh -> methods -> persistent_shutdown (dbh );
1491+ /* a persisted dbh will be freed when the resource is destructed. */
1492+ if (!(-- dbh -> refcount ) && !pdo_is_persisted (dbh )) {
1493+ if (!dbh -> is_closed ) {
1494+ dbh_shutdown (dbh );
1495+ }
1496+ dbh_free (dbh );
1497+ }
14351498 }
1499+
14361500 zend_object_std_dtor (std );
1437- dbh_free (dbh , 0 );
14381501}
14391502
14401503zend_object * pdo_dbh_new (zend_class_entry * ce )
@@ -1447,6 +1510,7 @@ zend_object *pdo_dbh_new(zend_class_entry *ce)
14471510 rebuild_object_properties (& dbh -> std );
14481511 dbh -> inner = ecalloc (1 , sizeof (pdo_dbh_t ));
14491512 dbh -> inner -> def_stmt_ce = pdo_dbstmt_ce ;
1513+ dbh -> inner -> refcount ++ ;
14501514
14511515 return & dbh -> std ;
14521516}
@@ -1457,7 +1521,10 @@ ZEND_RSRC_DTOR_FUNC(php_pdo_pdbh_dtor) /* {{{ */
14571521{
14581522 if (res -> ptr ) {
14591523 pdo_dbh_t * dbh = (pdo_dbh_t * )res -> ptr ;
1460- dbh_free (dbh , 1 );
1524+ if (!dbh -> refcount ) {
1525+ /* do not free if still referenced by pdo */
1526+ dbh_free (dbh );
1527+ }
14611528 res -> ptr = NULL ;
14621529 }
14631530}
0 commit comments