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
2 changes: 1 addition & 1 deletion ext/pdo/pdo_dbh.c
Original file line number Diff line number Diff line change
Expand Up @@ -1606,7 +1606,7 @@ static void pdo_dbh_free_storage(zend_object *std)
dbh->in_txn = false;
}

if (dbh->is_persistent && dbh->driver_data && dbh->methods && dbh->methods->persistent_shutdown) {
if (dbh->is_persistent && dbh->refcount <= 2 && dbh->driver_data && dbh->methods && dbh->methods->persistent_shutdown) {
dbh->methods->persistent_shutdown(dbh);
}
zend_object_std_dtor(std);
Expand Down
32 changes: 29 additions & 3 deletions ext/pdo_pgsql/pgsql_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,11 @@ static void pgsql_handle_closer(pdo_dbh_t *dbh) /* {{{ */
pefree(H->lob_streams, dbh->is_persistent);
H->lob_streams = NULL;
}
if (H->prepared_stmts) {
zend_hash_destroy(H->prepared_stmts);
pefree(H->prepared_stmts, 1);
H->prepared_stmts = NULL;
}
pdo_pgsql_cleanup_notice_callback(H);
if (H->server) {
PQfinish(H->server);
Expand Down Expand Up @@ -1398,11 +1403,28 @@ static void pdo_pgsql_request_shutdown(pdo_dbh_t *dbh)
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;

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

if(res) {
zend_string *name;

ZEND_HASH_MAP_FOREACH_STR_KEY(H->prepared_stmts, name) {
#ifndef HAVE_PQCLOSEPREPARED
char *q;
spprintf(&q, 0, "DEALLOCATE %s", ZSTR_VAL(name));
res = PQexec(H->server, q);
efree(q);
#else
res = PQclosePrepared(H->server, ZSTR_VAL(name));
#endif
PQclear(res);
} ZEND_HASH_FOREACH_END();
zend_hash_clean(H->prepared_stmts);

res = PQexec(H->server, "CLOSE ALL; SET SESSION AUTHORIZATION DEFAULT; RESET ALL; UNLISTEN *; "
"SELECT pg_advisory_unlock_all(); DISCARD TEMP; DISCARD SEQUENCES");

if (PQresultStatus(res) != PGRES_COMMAND_OK && PQstatus(H->server) == CONNECTION_OK) {
PQreset(H->server);
}
PQclear(res);
}
}

Expand Down Expand Up @@ -1523,6 +1545,10 @@ static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{
H->server = PQconnectdb(ZSTR_VAL(conn_str.s));
H->lob_streams = (HashTable *) pemalloc(sizeof(HashTable), dbh->is_persistent);
zend_hash_init(H->lob_streams, 0, NULL, NULL, 1);
if (dbh->is_persistent) {
H->prepared_stmts = (HashTable *) pemalloc(sizeof(HashTable), 1);
zend_hash_init(H->prepared_stmts, 0, NULL, NULL, 1);
}

if (tmp_user) {
zend_string_release_ex(tmp_user, 0);
Expand Down
7 changes: 6 additions & 1 deletion ext/pdo_pgsql/pgsql_statement.c
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,11 @@ static void pgsql_stmt_finish(pdo_pgsql_stmt *S, int fin_mode)
res = PQclosePrepared(H->server, S->stmt_name);
#endif
if (res) {
if (H->prepared_stmts && PQresultStatus(res) == PGRES_COMMAND_OK) {
zend_hash_str_del(H->prepared_stmts, S->stmt_name, strlen(S->stmt_name));
}
PQclear(res);
}

S->is_prepared = false;
}
}
Expand Down Expand Up @@ -339,6 +341,9 @@ static int pgsql_stmt_execute(pdo_stmt_t *stmt)
case PGRES_TUPLES_OK:
/* it worked */
S->is_prepared = true;
if (H->prepared_stmts) {
zend_hash_str_add_empty_element(H->prepared_stmts, S->stmt_name, strlen(S->stmt_name));
}
PQclear(S->result);
S->result = NULL;
break;
Expand Down
1 change: 1 addition & 0 deletions ext/pdo_pgsql/php_pdo_pgsql_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ typedef struct {
bool emulate_prepares;
bool disable_prepares;
HashTable *lob_streams;
HashTable *prepared_stmts;
zend_fcall_info_cache *notice_callback;
bool default_fetching_laziness;
zend_long default_chunk_size;
Expand Down
40 changes: 40 additions & 0 deletions ext/pdo_pgsql/tests/gh24461.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--TEST--
GH-24461 (Persistent connections: session reset keeps prepared statements)
--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()')->fetchColumn();

$pdo1->exec("SET application_name = 'gh24461'");
$pdo1->exec('CREATE TEMP TABLE gh24461_tmp (i int)');
$pdo1->exec('PREPARE gh24461_stmt AS SELECT 42');

$pdo1 = null;

$pdo2 = PDOTest::test_factory(__DIR__ . '/common.phpt');
$pid2 = (int) $pdo2->query('SELECT pg_backend_pid()')->fetchColumn();
var_dump($pid1 === $pid2);

var_dump($pdo2->query('SHOW application_name')->fetchColumn());
var_dump($pdo2->query("SELECT to_regclass('pg_temp.gh24461_tmp')")->fetchColumn());
var_dump($pdo2->query('EXECUTE gh24461_stmt')->fetchColumn());

$pdo2->exec('DEALLOCATE gh24461_stmt');
?>
--EXPECT--
bool(true)
string(0) ""
NULL
string(2) "42"
40 changes: 40 additions & 0 deletions ext/pdo_pgsql/tests/gh24461_aborted_transaction_close.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--TEST--
GH-24461 (Persistent connections: statements closed inside an aborted transaction are closed on reset)
--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, PDO::ATTR_EMULATE_PREPARES => false]));

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

$pdo = PDOTest::test_factory(__DIR__ . '/common.phpt');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pid1 = (int) $pdo->query('SELECT pg_backend_pid()')->fetchColumn();
$pdo->beginTransaction();
$stmt = $pdo->prepare("SELECT 'gh24461_aborted'");
$stmt->execute();
try {
$pdo->exec('SELECT 1/0');
} catch (PDOException $e) {
}
$stmt = null;
$pdo->rollBack();
$pdo = null;

$pdo = PDOTest::test_factory(__DIR__ . '/common.phpt');
$pid2 = (int) $pdo->query('SELECT pg_backend_pid()')->fetchColumn();
var_dump($pid1 === $pid2);
var_dump($pdo->query("SELECT count(*) FROM pg_prepared_statements WHERE statement = 'SELECT ''gh24461_aborted'''")->fetchColumn());
?>
--CREDITS--
Artem Ukrainskiy
--EXPECT--
bool(true)
string(1) "0"
37 changes: 37 additions & 0 deletions ext/pdo_pgsql/tests/gh24461_leftover_statement.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
--TEST--
GH-24461 (Persistent connections: statements outliving their PDO object are closed on reset)
--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';

class HoldingPDO extends PDO {
public $stmt;
}

$pdo1 = PDOTest::test_factory(__DIR__ . '/common.phpt', HoldingPDO::class);
$pid1 = (int) $pdo1->query('SELECT pg_backend_pid()')->fetchColumn();
$pdo1->stmt = $pdo1->prepare("SELECT 'gh24461'");
$pdo1->stmt->execute();

unset($pdo1);
var_dump(gc_collect_cycles() > 0);

$pdo2 = PDOTest::test_factory(__DIR__ . '/common.phpt');
$pid2 = (int) $pdo2->query('SELECT pg_backend_pid()')->fetchColumn();
var_dump($pid1 === $pid2);
var_dump($pdo2->query("SELECT count(*) FROM pg_prepared_statements WHERE statement = 'SELECT ''gh24461'''")->fetchColumn());
?>
--EXPECT--
bool(true)
bool(true)
string(1) "0"
46 changes: 46 additions & 0 deletions ext/pdo_pgsql/tests/gh24461_shared_connection.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
--TEST--
GH-24461 (Persistent connections: session state is not reset while another PDO object still uses the connection)
--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');
$pdo2 = PDOTest::test_factory(__DIR__ . '/common.phpt');

$pid1 = (int) $pdo1->query('SELECT pg_backend_pid()')->fetchColumn();
$pid2 = (int) $pdo2->query('SELECT pg_backend_pid()')->fetchColumn();
var_dump($pid1 === $pid2);

$pdo2->exec("SET application_name = 'gh24461'");
$pdo2->exec('CREATE TEMP TABLE gh24461_shared (i int)');
$pdo2->query('SELECT pg_advisory_lock(24461)');
$stmt = $pdo2->prepare('SELECT 42');
$stmt->execute();
$stmt->closeCursor();

$pdo1 = null;

var_dump($pdo2->query('SHOW application_name')->fetchColumn());
var_dump($pdo2->query('SELECT count(*) FROM gh24461_shared')->fetchColumn());
var_dump($pdo2->query("SELECT count(*) FROM pg_locks WHERE locktype = 'advisory' AND objid = 24461 AND pid = pg_backend_pid()")->fetchColumn());
$stmt->execute();
var_dump($stmt->fetchColumn());

$pdo2->query('SELECT pg_advisory_unlock_all()');
?>
--EXPECT--
bool(true)
string(7) "gh24461"
string(1) "0"
string(1) "1"
string(2) "42"
Loading