Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion ext/pdo_pgsql/pgsql_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -1340,6 +1340,20 @@ static const zend_function_entry *pdo_pgsql_get_driver_methods(pdo_dbh_t *dbh, i
}
}

static void pdo_pgsql_request_shutdown(pdo_dbh_t *dbh)
{
PGresult *res;
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;

if(H->server) {
res = PQexec(H->server, "DISCARD ALL");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be nice to have a test with a transaction being interrupted, then forcing the shutdown wdyt ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a test case that "attempts to perform disconnection-equivalent processing after being disconnected from the outside." This is because session-level parameters and advisory locks have no concept of transactions.

For normal transactions, the previous implementation appears to correctly detect and initialize transactions, even if the BEGIN statement is issued independently with exec() without using beginTransaction():

return PQtransactionStatus(H->server) > PQTRANS_IDLE;

php-src/ext/pdo/pdo_dbh.c

Lines 1588 to 1591 in 1ee8dfd

if (dbh->driver_data && dbh->methods && dbh->methods->rollback && pdo_is_in_transaction(dbh)) {
dbh->methods->rollback(dbh);
dbh->in_txn = false;
}


if(res) {
PQclear(res);
}
}
}

static bool pdo_pgsql_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val)
{
bool bval;
Expand Down Expand Up @@ -1383,7 +1397,7 @@ static const struct pdo_dbh_methods pgsql_methods = {
pdo_pgsql_get_attribute,
pdo_pgsql_check_liveness, /* check_liveness */
pdo_pgsql_get_driver_methods, /* get_driver_methods */
NULL,
pdo_pgsql_request_shutdown,
pgsql_handle_in_transaction,
NULL, /* get_gc */
pdo_pgsql_scanner
Expand Down
55 changes: 55 additions & 0 deletions ext/pdo_pgsql/tests/session_state_reset.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
--TEST--
Persistent connections: session state reset when performing disconnect-equivalent processing (general case)
--EXTENSIONS--
pdo_pgsql
--SKIPIF--
<?php
require __DIR__ . '/config.inc';
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
PDOTest::skip();
?>
--FILE--
<?php
putenv('PDOTEST_ATTR='.serialize([PDO::ATTR_PERSISTENT => true]));

require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';

$pdo1 = PDOTest::test_factory(__DIR__ . '/common.phpt');

$pid1 = (int)$pdo1
->query('select pg_backend_pid()::int;')
->fetchColumn(0);

$defaultValue = (int)$pdo1
->query('show log_min_duration_statement;')
->fetchColumn(0);

$setValue = $defaultValue + 1;

$pdo1->exec("set log_min_duration_statement = {$setValue};");

$pdo1 = null;

$pdo2 = PDOTest::test_factory(__DIR__ . '/common.phpt');

$pid2 = (int)$pdo2
->query('select pg_backend_pid()::int;')
->fetchColumn(0);

assert($pid1 === $pid2);

$expectedValue = (int)$pdo2
->query('show log_min_duration_statement;')
->fetchColumn(0);

echo "defaultValue: {$defaultValue}\n";
echo "setValue: {$setValue}\n";
echo "expectedValue: {$expectedValue}\n";
echo "expected value should be reset to default: " . (($expectedValue === $defaultValue) ? 'success' : 'failure') . "\n";

?>
--EXPECTF--
defaultValue: %i
setValue: %d
expectedValue: %i
expected value should be reset to default: success
51 changes: 51 additions & 0 deletions ext/pdo_pgsql/tests/session_state_reset_advisory_lock.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
--TEST--
Persistent connections: session state reset when performing disconnect-equivalent processing (advisory lock case)
--EXTENSIONS--
pdo_pgsql
--SKIPIF--
<?php
require __DIR__ . '/config.inc';
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
PDOTest::skip();
?>
--FILE--
<?php
putenv('PDOTEST_ATTR='.serialize([PDO::ATTR_PERSISTENT => true]));

require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';

$pdo1 = PDOTest::test_factory(__DIR__ . '/common.phpt');

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would it be possible to store SELECT pg_backend_pid() query before this one and the other and checking if they re equal ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added it to all the tests (now 3) related to this issue.

You were right. I was confused myself by the 3 tests, some of which keep the pid the same and some of which intentionally change it :'D

$pid1 = (int)$pdo1
->query('select pg_backend_pid()::int;')
->fetchColumn(0);

$lockResult1 = (bool)$pdo1
->query('select pg_try_advisory_lock(42)::int;')
->fetchColumn(0);

$pdo1 = null;

$dsn = getenv('PDO_PGSQL_TEST_DSN');
$dsn .= ';';
putenv('PDO_PGSQL_TEST_DSN='.$dsn);

$pdo2 = PDOTest::test_factory(__DIR__ . '/common.phpt');

$pid2 = (int)$pdo2
->query('select pg_backend_pid()::int;')
->fetchColumn(0);

assert($pid1 !== $pid2);

$lockResult2 = (bool)$pdo2
->query('select pg_try_advisory_lock(42)::int;')
->fetchColumn(0);

echo "lock1: " . ($lockResult1 ? 'success' : 'failure') . "\n";
echo "lock2: " . ($lockResult2 ? 'success' : 'failure') . "\n";

?>
--EXPECT--
lock1: success
lock2: success
52 changes: 52 additions & 0 deletions ext/pdo_pgsql/tests/session_state_reset_after_interrupted.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
--TEST--
Persistent connections: session state reset after backend termination (interrupted case)
--EXTENSIONS--
pdo_pgsql
--SKIPIF--
<?php
require __DIR__ . '/config.inc';
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
PDOTest::skip();
?>
--FILE--
<?php
putenv('PDOTEST_ATTR='.serialize([PDO::ATTR_PERSISTENT => true]));

require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';

$pdo1 = PDOTest::test_factory(__DIR__ . '/common.phpt');

$pid1 = (int)$pdo1
->query('select pg_backend_pid()::int;')
->fetchColumn(0);

$pid1 = (int)$pdo1
->query('select pg_backend_pid()::int;')
->fetchColumn(0);

$dsn = getenv('PDO_PGSQL_TEST_DSN');
$dsn .= ';';
putenv('PDO_PGSQL_TEST_DSN='.$dsn);

$pdo2 = PDOTest::test_factory(__DIR__ . '/common.phpt');

$pid2 = (int)$pdo2
->query('select pg_backend_pid()::int;')
->fetchColumn(0);

assert($pid1 !== $pid2);

$terminateResult = (bool)$pdo2
->query("select pg_terminate_backend({$pid1})::int")
->fetchColumn(0);

// Disconnect after being terminated by another connection
$pdo1 = null;

echo 'pid of pdo1: ' . $pid1 . "\n";
echo 'terminate result of pdo1 by pdo2: ' . ($terminateResult ? 'success' : 'failure') . "\n";

?>
--EXPECTF--
pid of pdo1: %d
terminate result of pdo1 by pdo2: success