@@ -221,6 +221,21 @@ static char *dsn_from_uri(char *uri, char *buf, size_t buflen) /* {{{ */
221
221
}
222
222
/* }}} */
223
223
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
+
224
239
/* {{{ */
225
240
PHP_METHOD (PDO , __construct )
226
241
{
@@ -297,7 +312,6 @@ PHP_METHOD(PDO, __construct)
297
312
if (options ) {
298
313
int plen = 0 ;
299
314
char * hashkey = NULL ;
300
- zend_resource * le ;
301
315
pdo_dbh_t * pdbh = NULL ;
302
316
zval * v ;
303
317
@@ -320,36 +334,22 @@ PHP_METHOD(PDO, __construct)
320
334
321
335
if (is_persistent ) {
322
336
/* 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 ))) {
340
341
/* need a brand new pdbh */
341
342
pdbh = pecalloc (1 , sizeof (* pdbh ), 1 );
342
343
343
- pdbh -> refcount = 1 ;
344
344
pdbh -> is_persistent = 1 ;
345
345
pdbh -> persistent_id = pemalloc (plen + 1 , 1 );
346
346
memcpy ((char * )pdbh -> persistent_id , hashkey , plen + 1 );
347
347
pdbh -> persistent_id_len = plen ;
348
348
pdbh -> def_stmt_ce = dbh -> def_stmt_ce ;
349
+ } else {
350
+ /* found viable dbh persisted */
351
+ call_factory = 0 ;
349
352
}
350
- }
351
-
352
- if (pdbh ) {
353
353
efree (dbh );
354
354
/* switch over to the persistent one */
355
355
Z_PDO_OBJECT_P (object )-> inner = pdbh ;
@@ -393,6 +393,8 @@ PHP_METHOD(PDO, __construct)
393
393
/* register in the persistent list etc. */
394
394
/* we should also need to replace the object store entry,
395
395
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. */
396
398
if ((zend_register_persistent_resource (
397
399
(char * )dbh -> persistent_id , dbh -> persistent_id_len , dbh , php_pdo_list_entry ())) == NULL ) {
398
400
php_error_docref (NULL , E_ERROR , "Failed to register persistent entry" );
@@ -422,9 +424,6 @@ PHP_METHOD(PDO, __construct)
422
424
}
423
425
424
426
/* the connection failed; things will tidy up in free_storage */
425
- if (is_persistent ) {
426
- dbh -> refcount -- ;
427
- }
428
427
429
428
/* XXX raise exception */
430
429
zend_restore_error_handling (& zeh );
@@ -587,6 +586,9 @@ PHP_METHOD(PDO, prepare)
587
586
588
587
589
588
static bool pdo_is_in_transaction (pdo_dbh_t * dbh ) {
589
+ if (dbh -> is_closed ) {
590
+ return false;
591
+ }
590
592
if (dbh -> methods -> in_transaction ) {
591
593
return dbh -> methods -> in_transaction (dbh );
592
594
}
@@ -684,6 +686,17 @@ PHP_METHOD(PDO, inTransaction)
684
686
}
685
687
/* }}} */
686
688
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
+
687
700
PDO_API bool pdo_get_long_param (zend_long * lval , zval * value )
688
701
{
689
702
switch (Z_TYPE_P (value )) {
@@ -1027,8 +1040,6 @@ PHP_METHOD(PDO, errorCode)
1027
1040
1028
1041
ZEND_PARSE_PARAMETERS_NONE ();
1029
1042
1030
- PDO_CONSTRUCT_CHECK ;
1031
-
1032
1043
if (dbh -> query_stmt ) {
1033
1044
RETURN_STRING (dbh -> query_stmt -> error_code );
1034
1045
}
@@ -1056,8 +1067,6 @@ PHP_METHOD(PDO, errorInfo)
1056
1067
1057
1068
ZEND_PARSE_PARAMETERS_NONE ();
1058
1069
1059
- PDO_CONSTRUCT_CHECK ;
1060
-
1061
1070
array_init (return_value );
1062
1071
1063
1072
if (dbh -> query_stmt ) {
@@ -1068,7 +1077,8 @@ PHP_METHOD(PDO, errorInfo)
1068
1077
if (!strncmp (dbh -> error_code , PDO_ERR_NONE , sizeof (PDO_ERR_NONE ))) goto fill_array ;
1069
1078
}
1070
1079
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 ) {
1072
1082
dbh -> methods -> fetch_err (dbh , dbh -> query_stmt , return_value );
1073
1083
}
1074
1084
@@ -1366,26 +1376,55 @@ void pdo_dbh_init(int module_number)
1366
1376
pdo_dbh_object_handlers .get_gc = dbh_get_gc ;
1367
1377
}
1368
1378
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 )
1370
1381
{
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
+ }
1372
1386
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 );
1376
1389
}
1377
1390
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
+
1379
1422
#if ZEND_DEBUG
1380
- ZEND_ASSERT (! free_persistent || ( dbh -> refcount == 1 ) );
1423
+ ZEND_ASSERT (dbh -> refcount == 0 );
1381
1424
#endif
1382
- if (!free_persistent && (-- dbh -> refcount )) {
1383
- return ;
1384
- }
1385
- }
1386
1425
1387
- if (dbh -> methods ) {
1388
- dbh -> methods -> closer (dbh );
1426
+ if (dbh -> driver ) {
1427
+ dbh_shutdown (dbh );
1389
1428
}
1390
1429
1391
1430
if (dbh -> data_source ) {
@@ -1416,25 +1455,45 @@ static void dbh_free(pdo_dbh_t *dbh, bool free_persistent)
1416
1455
pefree (dbh , dbh -> is_persistent );
1417
1456
}
1418
1457
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
+
1419
1471
static void pdo_dbh_free_storage (zend_object * std )
1420
1472
{
1421
1473
pdo_dbh_t * dbh = php_pdo_dbh_fetch_inner (std );
1422
1474
1423
1475
/* 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
+ }
1427
1480
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
+ }
1432
1489
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
+ }
1435
1494
}
1495
+
1436
1496
zend_object_std_dtor (std );
1437
- dbh_free (dbh , 0 );
1438
1497
}
1439
1498
1440
1499
zend_object * pdo_dbh_new (zend_class_entry * ce )
@@ -1447,6 +1506,7 @@ zend_object *pdo_dbh_new(zend_class_entry *ce)
1447
1506
rebuild_object_properties (& dbh -> std );
1448
1507
dbh -> inner = ecalloc (1 , sizeof (pdo_dbh_t ));
1449
1508
dbh -> inner -> def_stmt_ce = pdo_dbstmt_ce ;
1509
+ dbh -> inner -> refcount ++ ;
1450
1510
1451
1511
return & dbh -> std ;
1452
1512
}
@@ -1457,7 +1517,10 @@ ZEND_RSRC_DTOR_FUNC(php_pdo_pdbh_dtor) /* {{{ */
1457
1517
{
1458
1518
if (res -> ptr ) {
1459
1519
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
+ }
1461
1524
res -> ptr = NULL ;
1462
1525
}
1463
1526
}
0 commit comments