@@ -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,8 +424,9 @@ 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
+ if (dbh -> methods ) {
428
+ /* free any resources allocated during failed factory */
429
+ dbh -> methods -> closer (dbh );
427
430
}
428
431
429
432
/* XXX raise exception */
@@ -587,6 +590,9 @@ PHP_METHOD(PDO, prepare)
587
590
588
591
589
592
static bool pdo_is_in_transaction (pdo_dbh_t * dbh ) {
593
+ if (dbh -> is_closed ) {
594
+ return false;
595
+ }
590
596
if (dbh -> methods -> in_transaction ) {
591
597
return dbh -> methods -> in_transaction (dbh );
592
598
}
@@ -684,6 +690,17 @@ PHP_METHOD(PDO, inTransaction)
684
690
}
685
691
/* }}} */
686
692
693
+ /* {{{ Determine if connected */
694
+ PHP_METHOD (PDO , isConnected )
695
+ {
696
+ pdo_dbh_t * dbh = Z_PDO_DBH_P (ZEND_THIS );
697
+
698
+ ZEND_PARSE_PARAMETERS_NONE ();
699
+
700
+ RETURN_BOOL (!dbh -> is_closed );
701
+ }
702
+ /* }}} */
703
+
687
704
PDO_API bool pdo_get_long_param (zend_long * lval , zval * value )
688
705
{
689
706
switch (Z_TYPE_P (value )) {
@@ -1027,8 +1044,6 @@ PHP_METHOD(PDO, errorCode)
1027
1044
1028
1045
ZEND_PARSE_PARAMETERS_NONE ();
1029
1046
1030
- PDO_CONSTRUCT_CHECK ;
1031
-
1032
1047
if (dbh -> query_stmt ) {
1033
1048
RETURN_STRING (dbh -> query_stmt -> error_code );
1034
1049
}
@@ -1056,8 +1071,6 @@ PHP_METHOD(PDO, errorInfo)
1056
1071
1057
1072
ZEND_PARSE_PARAMETERS_NONE ();
1058
1073
1059
- PDO_CONSTRUCT_CHECK ;
1060
-
1061
1074
array_init (return_value );
1062
1075
1063
1076
if (dbh -> query_stmt ) {
@@ -1068,7 +1081,8 @@ PHP_METHOD(PDO, errorInfo)
1068
1081
if (!strncmp (dbh -> error_code , PDO_ERR_NONE , sizeof (PDO_ERR_NONE ))) goto fill_array ;
1069
1082
}
1070
1083
1071
- if (dbh -> methods -> fetch_err ) {
1084
+ /* Driver-implemented error is not available once database is shutdown. */
1085
+ if (dbh -> driver && dbh -> methods -> fetch_err ) {
1072
1086
dbh -> methods -> fetch_err (dbh , dbh -> query_stmt , return_value );
1073
1087
}
1074
1088
@@ -1366,26 +1380,55 @@ void pdo_dbh_init(int module_number)
1366
1380
pdo_dbh_object_handlers .get_gc = dbh_get_gc ;
1367
1381
}
1368
1382
1369
- static void dbh_free (pdo_dbh_t * dbh , bool free_persistent )
1383
+ /* Disconnect from the database and free associated driver. */
1384
+ static void dbh_shutdown (pdo_dbh_t * dbh )
1370
1385
{
1371
- int i ;
1386
+ if (dbh -> driver_data && dbh -> methods && dbh -> methods -> rollback && pdo_is_in_transaction (dbh )) {
1387
+ dbh -> methods -> rollback (dbh );
1388
+ dbh -> in_txn = false;
1389
+ }
1372
1390
1373
- if (dbh -> query_stmt ) {
1374
- zval_ptr_dtor (& dbh -> query_stmt_zval );
1375
- dbh -> query_stmt = NULL ;
1391
+ if (dbh -> methods ) {
1392
+ dbh -> methods -> closer (dbh );
1376
1393
}
1377
1394
1378
- if (dbh -> is_persistent ) {
1395
+ /* Do not permit reference to driver to remain past closer(), which
1396
+ * is responsible for both disconnecting the db and free-ing allocations. */
1397
+ dbh -> driver = NULL ;
1398
+ dbh -> is_closed = true;
1399
+ }
1400
+
1401
+ /* {{{ Disconnect from the database. */
1402
+ PHP_METHOD (PDO , disconnect )
1403
+ {
1404
+ pdo_dbh_t * dbh = Z_PDO_DBH_P (ZEND_THIS );
1405
+
1406
+ ZEND_PARSE_PARAMETERS_NONE ();
1407
+
1408
+ PDO_DBH_CLEAR_ERR ();
1409
+ PDO_CONSTRUCT_CHECK ;
1410
+
1411
+ dbh_shutdown (dbh );
1412
+
1413
+ PDO_HANDLE_DBH_ERR ();
1414
+
1415
+ RETURN_TRUE ;
1416
+ }
1417
+ /* }}} */
1418
+
1419
+ /* Free the database when the last pdo object referencing it is freed
1420
+ * or when it has been registered as a php resource, i.e. is persistent,
1421
+ * and the resource is destructed, whichever comes last. */
1422
+ static void dbh_free (pdo_dbh_t * dbh )
1423
+ {
1424
+ int i ;
1425
+
1379
1426
#if ZEND_DEBUG
1380
- ZEND_ASSERT (! free_persistent || ( dbh -> refcount == 1 ) );
1427
+ ZEND_ASSERT (dbh -> refcount == 0 );
1381
1428
#endif
1382
- if (!free_persistent && (-- dbh -> refcount )) {
1383
- return ;
1384
- }
1385
- }
1386
1429
1387
- if (dbh -> methods ) {
1388
- dbh -> methods -> closer (dbh );
1430
+ if (dbh -> driver ) {
1431
+ dbh_shutdown (dbh );
1389
1432
}
1390
1433
1391
1434
if (dbh -> data_source ) {
@@ -1416,25 +1459,45 @@ static void dbh_free(pdo_dbh_t *dbh, bool free_persistent)
1416
1459
pefree (dbh , dbh -> is_persistent );
1417
1460
}
1418
1461
1462
+ /* Whether the given database handler is presently registered as a resource. */
1463
+ static bool pdo_is_persisted (pdo_dbh_t * dbh )
1464
+ {
1465
+ pdo_dbh_t * pdbh = NULL ;
1466
+
1467
+ if (dbh -> persistent_id != NULL ) {
1468
+ pdbh = pdo_list_entry_from_key (dbh -> persistent_id , dbh -> persistent_id_len );
1469
+ return dbh == pdbh ;
1470
+ }
1471
+
1472
+ return false;
1473
+ }
1474
+
1419
1475
static void pdo_dbh_free_storage (zend_object * std )
1420
1476
{
1421
1477
pdo_dbh_t * dbh = php_pdo_dbh_fetch_inner (std );
1422
1478
1423
1479
/* dbh might be null if we OOMed during object initialization. */
1424
- if (!dbh ) {
1425
- return ;
1426
- }
1480
+ if (dbh ) {
1481
+ if (dbh -> is_persistent && dbh -> methods && dbh -> methods -> persistent_shutdown ) {
1482
+ dbh -> methods -> persistent_shutdown (dbh );
1483
+ }
1427
1484
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
- }
1485
+ /* stmt is not persistent, even if dbh is, so it must be freed with pdo.
1486
+ * Consider copying stmt error code to dbh at this point, seemingly the reason
1487
+ * that the stmt is even being held, or even better, to do that at the time of
1488
+ * error and remove the reference altogether. */
1489
+ if (dbh -> query_stmt ) {
1490
+ zval_ptr_dtor (& dbh -> query_stmt_zval );
1491
+ dbh -> query_stmt = NULL ;
1492
+ }
1432
1493
1433
- if (dbh -> is_persistent && dbh -> methods && dbh -> methods -> persistent_shutdown ) {
1434
- dbh -> methods -> persistent_shutdown (dbh );
1494
+ /* a persisted dbh will be freed when the resource is destructed. */
1495
+ if (!(-- dbh -> refcount ) && !pdo_is_persisted (dbh )) {
1496
+ dbh_free (dbh );
1497
+ }
1435
1498
}
1499
+
1436
1500
zend_object_std_dtor (std );
1437
- dbh_free (dbh , 0 );
1438
1501
}
1439
1502
1440
1503
zend_object * pdo_dbh_new (zend_class_entry * ce )
@@ -1447,6 +1510,7 @@ zend_object *pdo_dbh_new(zend_class_entry *ce)
1447
1510
rebuild_object_properties (& dbh -> std );
1448
1511
dbh -> inner = ecalloc (1 , sizeof (pdo_dbh_t ));
1449
1512
dbh -> inner -> def_stmt_ce = pdo_dbstmt_ce ;
1513
+ dbh -> inner -> refcount ++ ;
1450
1514
1451
1515
return & dbh -> std ;
1452
1516
}
@@ -1457,7 +1521,10 @@ ZEND_RSRC_DTOR_FUNC(php_pdo_pdbh_dtor) /* {{{ */
1457
1521
{
1458
1522
if (res -> ptr ) {
1459
1523
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
+ }
1461
1528
res -> ptr = NULL ;
1462
1529
}
1463
1530
}
0 commit comments