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
1 change: 0 additions & 1 deletion ext/mysqli/mysqli_mysqlnd.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,5 @@
#define mysqli_stmt_close(c, implicit) mysqlnd_stmt_close((c), (implicit))
#define mysqli_free_result(r, implicit) mysqlnd_free_result((r), (implicit))
#define mysqli_async_query(c, q, l) mysqlnd_async_query((c), (q), (l))
#define mysqli_change_user_silent(c, u, p, d, p_len) mysqlnd_change_user_ex((c), (u), (p), (d), true, (size_t)(p_len))

#endif
8 changes: 1 addition & 7 deletions ext/mysqli/mysqli_nonapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,8 @@ void mysqli_common_connect(INTERNAL_FUNCTION_PARAMETERS, bool is_real_connect, b
mysql->mysql = zend_ptr_stack_pop(&plist->free_links);

MyG(num_inactive_persistent)--;
/* reset variables */

#ifndef MYSQLI_NO_CHANGE_USER_ON_PCONNECT

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are we dropping this configuration? Was this part of the RFC?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a good point; it was not a part of the RFC.

When I originally wrote this, I could not find any users of MYSQLI_NO_CHANGE_USER_ON_PCONNECT, but we could preserve it - it would just be a bit of a misnomer. We could also introduce a new name for it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This needs to be restored

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't see how this was not part of the RFC.

The RFC specifies that mysqlnd will call COM_RESET_CONNECTION when reusing persistent connections, which is happening in mysqlnd_restart_psession() which is outside of the #ifdef. Since MySQLnd is the only supported driver for MySQLi, this change is just an obvious cleanup removing useless code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It doesn't say anything about removal of mysqli_change_user_silent (see my comment below about the side effect it has got and likely introduction of new state leak.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It doesn't say anything about removal of mysqli_change_user_silent

My understanding is that the change user is just a poor man’s reset and there is no reason to change the user and then reset the connection when just resetting it is sufficient.


Now if the active database is not reset by the COM_RESET_CONNECTION that is a bug in the implementation that @ericnorris should verify and include a test for. But any fix must then be applied to both PDO and MySQLi, not just MySQLi. And ideally it should be fixed in mysqlnd.

if (!mysqli_change_user_silent(mysql->mysql, username, passwd, dbname, passwd_len)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So this actually introduce new state leak. Before this changes the default database was restored but after this, it is kept because COM_RESET_CONNECTION does not seem to touch it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks actually like a significant regression because I think the impact is following:

// Request A
$db = mysqli_connect('p:localhost', $user, $pass, 'app');
$db->select_db('archive'); // or $db->query('USE archive');
// request ends, connection goes back to the pool

// Request B
$db = mysqli_connect('p:localhost', $user, $pass, 'app');  // same hash key => reuse
$db->query('SELECT * FROM users');   // runs against archive.users, not app.users

If this is really the case, then this PR definitely needs a proper review so will need to allocate more time to go through it properly and think about other consequences to make sure there aren't other issues.

#else
if (!mysql_ping(mysql->mysql)) {
#endif
mysqlnd_restart_psession(mysql->mysql);
if (!mysqlnd_restart_psession(mysql->mysql)) {
MyG(num_active_persistent)++;

/* clear error */
Expand Down
93 changes: 93 additions & 0 deletions ext/mysqli/tests/mysqli_pconn_preserved.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
--TEST--
mysqli: reusing a persistent connection re-applies its connection config after reset

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I haven't been immersed in this PR, but my first question is this: why do we need new mysqli tests? Doesn't this functionality already exist in mysqli?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We need new mysqli tests because the previous functionality did not cause the character set to be reset.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

--EXTENSIONS--
mysqli
--SKIPIF--
<?php
require_once 'skipifconnectfailure.inc';
?>
--FILE--
<?php
// COM_RESET_CONNECTION reverts the session to server defaults; mysqlnd should
// then re-apply the connection config, so a reused connection stays configured.
Comment on lines +11 to +12

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What is the purpose of this comment?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This comment - which I wrote - is meant to explain the goal of this test in a little more detail. As noted, COM_RESET_CONNECTION will cause the connection to revert to server defaults (like the character set, or wiping away the results of any MYSQLI_INIT_COMMAND commands), and we want to ensure they are correctly restored when reusing the connection.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Then it belongs in the test header, not here. But you used the word "connection config" and "configured" which isn't well-defined anywhere nor is generally used in this context. If you want to keep it, add it in the test header section and make it more explicit what it tests for.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Understood. Do you have any better suggestion for the terminology to use around describing "the set of options specified at the time of connection" and "state that the user may have introduced post-connection". The former should always be preserved, the latter should always be reset.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You can just list them out. The documentation does the same https://www.php.net/manual/en/mysqli.persistconns.php

"the set of options specified at the time of connection" -> It's good to list which options get restored. Be explicit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Doesn't that run the risk of "lying"? I thought it would be more descriptive to give a name to the set, and thus the set tested by the code can grow and shrink without needing to update the comment.


require_once 'connect.inc';

$host = 'p:' . $host;

// Open a persistent connection carrying both a charset and an init command.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please collapse the code comment into the function name

function connect_configured($host, $user, $passwd, $db, $port, $socket) {
$link = mysqli_init();
mysqli_options($link, MYSQLI_SET_CHARSET_NAME, 'latin1');
mysqli_options($link, MYSQLI_INIT_COMMAND, 'SET @init_marker = 42');
Comment on lines +21 to +22

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are these only executed when a persistent connection is opened for the first time?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That is correct, and they are lost when we issue the COM_RESET_CONNECTION command. The restart_psession function ensures we restore them.


if (!mysqli_real_connect($link, $host, $user, $passwd, $db, $port, $socket)) {
return false;
}

return $link;
}

// Establish a persistent handle with the above configuration.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please remove all unneeded comments. Clause loves to add comments.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I actually wrote these comments, because I find that it helps visually break up the test into its constituent parts. Each comment describes the goal of the next few lines.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I find the opposite, code comments make it more difficult for me to read the code. Code comments often lie, are written in a non-technical language, or are too verbose. Comments should only be used when strictly necessary to understand the code and when the variable name or function name cannot be improved.

In unit tests, code comments are treated a bit more loosely, but the comments in this file do not help me much. Some comments can stay, e.g. $link->close() is nice.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I see. It may be difficult and require a bit of back-and-forth to understand which comments you feel are useful and which are not. Would you mind taking a stab at making these tests more to your liking? You could also address some of the other nits you pointed out. I'm not precious about the tests.

If not, do you consider this blocking, or would you be open to restyling the tests later on?

$link = connect_configured($host, $user, $passwd, $db, $port, $socket);

if (!$link) {
printf("[001] Cannot connect\n");
}
Comment on lines +34 to +36

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I feel like since this is a new test, you could just use the exception mode.


$thread_id = mysqli_thread_id($link);

// Sanity check that the charset was configured.
if (mysqli_character_set_name($link) !== 'latin1') {
printf("[002] Charset should be latin1 at connect, got %s\n", mysqli_character_set_name($link));
}

// Sanity check that the init command was executed.
$marker = mysqli_query($link, "SELECT @init_marker")->fetch_row()[0];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
$marker = mysqli_query($link, "SELECT @init_marker")->fetch_row()[0];
$marker = mysqli_query($link, "SELECT @init_marker")->fetch_column();


if ($marker != 42) {
printf("[003] Init command should have set @init_marker at connect, got %s\n", var_export($marker, true));
}

Comment thread
ericnorris marked this conversation as resolved.
// Drift both values away from the configuration, so the checks after reuse prove
// the config was re-applied by the reset rather than merely carried over.
mysqli_set_charset($link, 'ascii');
mysqli_query($link, "SET @init_marker = 99");
Comment thread
kamil-tekiela marked this conversation as resolved.

// Return the connection to the pool.
mysqli_close($link);

// Reopen the same persistent connection, which should be reset, but preserve
// the options from the initial configuration.
Comment on lines +60 to +61

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

What does this mean? What do you test here? What does this test have to do with persistent connections? Shouldn't it test that the configuration is cleared?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, the connection configuration should be preserved, and any post-connection changes should be reset.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

By "connection configuration" you mean the two initial commands you execute via options? MYSQLI_SET_CHARSET_NAME and MYSQLI_INIT_COMMAND?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep! And any others, if we're missing them.

$link = connect_configured($host, $user, $passwd, $db, $port, $socket);

// Compare the thread IDs to ensure the connection was reused.
if (mysqli_thread_id($link) !== $thread_id) {
printf("[004] Expected the pooled connection to be reused\n");
}

// Check the charset to ensure the configured one is back in effect.
if (mysqli_character_set_name($link) !== 'latin1') {
printf("[005] Charset should be re-applied to latin1, got %s\n", mysqli_character_set_name($link));
}

// Check the server-side charset to ensure it agrees with the client.
$server = mysqli_query($link, "SELECT @@session.character_set_connection")->fetch_row()[0];

if ($server !== 'latin1') {
printf("[006] Server charset should be latin1, got %s\n", $server);
}

// Check the session variable to ensure the init command was re-executed.
$marker = mysqli_query($link, "SELECT @init_marker")->fetch_row()[0];

if ($marker != 42) {
printf("[007] Init command should be re-run after reuse, got %s\n", var_export($marker, true));
}

mysqli_close($link);

echo "done!";
?>
--EXPECT--
done!
Comment on lines +1 to +93

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
--TEST--
mysqli: reusing a persistent connection re-applies its connection config after reset
--EXTENSIONS--
mysqli
--SKIPIF--
<?php
require_once 'skipifconnectfailure.inc';
?>
--FILE--
<?php
// COM_RESET_CONNECTION reverts the session to server defaults; mysqlnd should
// then re-apply the connection config, so a reused connection stays configured.
require_once 'connect.inc';
$host = 'p:' . $host;
// Open a persistent connection carrying both a charset and an init command.
function connect_configured($host, $user, $passwd, $db, $port, $socket) {
$link = mysqli_init();
mysqli_options($link, MYSQLI_SET_CHARSET_NAME, 'latin1');
mysqli_options($link, MYSQLI_INIT_COMMAND, 'SET @init_marker = 42');
if (!mysqli_real_connect($link, $host, $user, $passwd, $db, $port, $socket)) {
return false;
}
return $link;
}
// Establish a persistent handle with the above configuration.
$link = connect_configured($host, $user, $passwd, $db, $port, $socket);
if (!$link) {
printf("[001] Cannot connect\n");
}
$thread_id = mysqli_thread_id($link);
// Sanity check that the charset was configured.
if (mysqli_character_set_name($link) !== 'latin1') {
printf("[002] Charset should be latin1 at connect, got %s\n", mysqli_character_set_name($link));
}
// Sanity check that the init command was executed.
$marker = mysqli_query($link, "SELECT @init_marker")->fetch_row()[0];
if ($marker != 42) {
printf("[003] Init command should have set @init_marker at connect, got %s\n", var_export($marker, true));
}
// Drift both values away from the configuration, so the checks after reuse prove
// the config was re-applied by the reset rather than merely carried over.
mysqli_set_charset($link, 'ascii');
mysqli_query($link, "SET @init_marker = 99");
// Return the connection to the pool.
mysqli_close($link);
// Reopen the same persistent connection, which should be reset, but preserve
// the options from the initial configuration.
$link = connect_configured($host, $user, $passwd, $db, $port, $socket);
// Compare the thread IDs to ensure the connection was reused.
if (mysqli_thread_id($link) !== $thread_id) {
printf("[004] Expected the pooled connection to be reused\n");
}
// Check the charset to ensure the configured one is back in effect.
if (mysqli_character_set_name($link) !== 'latin1') {
printf("[005] Charset should be re-applied to latin1, got %s\n", mysqli_character_set_name($link));
}
// Check the server-side charset to ensure it agrees with the client.
$server = mysqli_query($link, "SELECT @@session.character_set_connection")->fetch_row()[0];
if ($server !== 'latin1') {
printf("[006] Server charset should be latin1, got %s\n", $server);
}
// Check the session variable to ensure the init command was re-executed.
$marker = mysqli_query($link, "SELECT @init_marker")->fetch_row()[0];
if ($marker != 42) {
printf("[007] Init command should be re-run after reuse, got %s\n", var_export($marker, true));
}
mysqli_close($link);
echo "done!";
?>
--EXPECT--
done!
--TEST--
mysqli: reusing a persistent connection should reapply MYSQLI_SET_CHARSET_NAME and MYSQLI_INIT_COMMAND options
--EXTENSIONS--
mysqli
--SKIPIF--
<?php
require_once 'skipifconnectfailure.inc';
?>
--FILE--
<?php
require_once 'connect.inc';
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
function connect_with_options($host, $user, $passwd, $db, $port, $socket): mysqli {
$link = new mysqli();
$link->options(MYSQLI_SET_CHARSET_NAME, 'latin1');
$link->options(MYSQLI_INIT_COMMAND, 'SET @init_marker = 42');
$link->connect('p:' . $host, $user, $passwd, $db, $port, $socket);
return $link;
}
$link = connect_with_options($host, $user, $passwd, $db, $port, $socket);
$connection_id = $link->query("SELECT CONNECTION_ID()")->fetch_column();
if ($link->character_set_name() !== 'latin1') {
printf("[001] Charset should be latin1 at connect, got %s\n", $link->character_set_name());
}
$marker = $link->query("SELECT @init_marker")->fetch_column();
if ($marker != 42) {
printf("[002] Init command should have set @init_marker at connect, got %s\n", var_export($marker, true));
}
// Drift both values away from the configuration, so the checks after reuse prove
// the config was reapplied by the reset rather than merely carried over.
$link->set_charset('ascii');
$link->query("SET @init_marker = 99");
// Return the connection to the pool.
$link->close();
// Reopen the same persistent connection, which should be reset but have the same options reapplied.
$link = connect_with_options($host, $user, $passwd, $db, $port, $socket);
if ($link->query("SELECT CONNECTION_ID()")->fetch_column() !== $connection_id) {
printf("[003] Expected the pooled connection to be reused\n");
}
if ($link->character_set_name() !== 'latin1') {
printf("[004] Charset should be reapplied to latin1, got %s\n", $link->character_set_name());
}
// Check the server-side charset to ensure it agrees with the client.
$server = $link->query("SELECT @@session.character_set_connection")->fetch_column();
if ($server !== 'latin1') {
printf("[005] Server charset should be latin1, got %s\n", $server);
}
$marker = $link->query("SELECT @init_marker")->fetch_column();
if ($marker != 42) {
printf("[006] Init command should be re-run after reuse, got %s\n", var_export($marker, true));
}
echo "done!";
?>
--EXPECT--
done!

95 changes: 95 additions & 0 deletions ext/mysqli/tests/mysqli_pconn_reset.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
--TEST--
mysqli: reusing a persistent connection resets its session state (COM_RESET_CONNECTION)
--EXTENSIONS--
mysqli
--SKIPIF--
<?php
require_once 'skipifconnectfailure.inc';
?>
--FILE--
<?php
// Reusing a pooled persistent connection sends COM_RESET_CONNECTION, which must
// wipe all existing session state.

require_once 'connect.inc';

$host = 'p:' . $host;

// Establish a persistent handle with some session state.
$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);

if (!$link) {
printf("[001] Cannot connect\n");
}

$thread_id = mysqli_thread_id($link);

// Pick a charset the connection does not already use, so the change is observable.
$default = mysqli_character_set_name($link);

if ($default === 'latin1') {
printf("[002] Test needs a default charset other than latin1, got %s\n", $default);
}
Comment on lines +27 to +32

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Where does the "Pick a charset the connection" happen?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I believe this would be whatever the MySQL default is, it just can't be latin1 for the purposes of this test.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So the test doesn't pick a charset? This comment is misleading. This means the test fails when MySQL uses latin1 as the default charset. I belive the test should actually select the charset that won't conflict.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure I see how the comment is misleading - the test does pick a charset, it picks latin1, and fails if the server is already set to latin1. Are you suggesting that we dynamically pick a charset that is guaranteed to not be the server default?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, that's how I understood this comment.


mysqli_query($link, "SET @test_var = 42");
mysqli_query($link, "CREATE TEMPORARY TABLE test_reset_tmp (id INT)");

mysqli_query($link, "DROP TABLE IF EXISTS test_reset_trx");
mysqli_query($link, "CREATE TABLE test_reset_trx (id INT) ENGINE=InnoDB");

mysqli_begin_transaction($link);
mysqli_query($link, "INSERT INTO test_reset_trx VALUES (1)");

// Change the charset after connecting, which is not part of the connection config.
mysqli_set_charset($link, 'latin1');

// Return the connection to the pool.
mysqli_close($link);

// Reopen the same persistent connection, which should be reset.
$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);

// Compare the thread IDs to ensure the connection was reused.
if (mysqli_thread_id($link) !== $thread_id) {
printf("[003] Expected the pooled connection to be reused\n");
}

// Check the session variable to ensure it is no longer present.
$v = mysqli_query($link, "SELECT @test_var")->fetch_row()[0];

@kamil-tekiela kamil-tekiela Aug 4, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
$v = mysqli_query($link, "SELECT @test_var")->fetch_row()[0];
$v = mysqli_query($link, "SELECT @test_var")->fetch_column();


if ($v !== null) {
printf("[004] User variable should be reset, got %s\n", var_export($v, true));
}

// Check the temporary table to ensure it is no longer present.
if (@mysqli_query($link, "SELECT 1 FROM test_reset_tmp") !== false) {
printf("[005] Temporary table should not exist after reset\n");
}

// Check to see we are no longer in a transaction.
$rows = mysqli_query($link, "SELECT COUNT(*) FROM test_reset_trx")->fetch_row()[0];

if ($rows != 0) {
printf("[006] Transaction should have been rolled back, found %d row(s)\n", $rows);
}

// A charset set after connecting is not part of the config, so it is not restored.
if (mysqli_character_set_name($link) !== $default) {
printf("[007] Charset should be reset to %s, got %s\n", $default, mysqli_character_set_name($link));
}

// mysqlnd's cached charset (used for escaping) must still agree with the server.
$server = mysqli_query($link, "SELECT @@session.character_set_connection")->fetch_row()[0];

if (mysqli_character_set_name($link) !== $server) {
printf("[008] Client charset %s disagrees with server %s\n", mysqli_character_set_name($link), $server);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't understand the whole thing with charsets. Does COM_RESET_CONNECTION reset the server and client charsets? What charset do we expect here and why? This should be made clearer with SELECT @@session.character_set_connection = 'latin1' and $mysqli->character_set_name() === 'latin1,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, COM_RESET_CONNECTION resets the server charset. Your suggestion may make this clearer, however.


// Clean up the non-temporary table.
mysqli_query($link, "DROP TABLE IF EXISTS test_reset_trx");
mysqli_close($link);
Comment on lines +89 to +90

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should be in a CLEANUP section.


echo "done!";
?>
--EXPECT--
done!
Comment on lines +1 to +95

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
--TEST--
mysqli: reusing a persistent connection resets its session state (COM_RESET_CONNECTION)
--EXTENSIONS--
mysqli
--SKIPIF--
<?php
require_once 'skipifconnectfailure.inc';
?>
--FILE--
<?php
// Reusing a pooled persistent connection sends COM_RESET_CONNECTION, which must
// wipe all existing session state.
require_once 'connect.inc';
$host = 'p:' . $host;
// Establish a persistent handle with some session state.
$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);
if (!$link) {
printf("[001] Cannot connect\n");
}
$thread_id = mysqli_thread_id($link);
// Pick a charset the connection does not already use, so the change is observable.
$default = mysqli_character_set_name($link);
if ($default === 'latin1') {
printf("[002] Test needs a default charset other than latin1, got %s\n", $default);
}
mysqli_query($link, "SET @test_var = 42");
mysqli_query($link, "CREATE TEMPORARY TABLE test_reset_tmp (id INT)");
mysqli_query($link, "DROP TABLE IF EXISTS test_reset_trx");
mysqli_query($link, "CREATE TABLE test_reset_trx (id INT) ENGINE=InnoDB");
mysqli_begin_transaction($link);
mysqli_query($link, "INSERT INTO test_reset_trx VALUES (1)");
// Change the charset after connecting, which is not part of the connection config.
mysqli_set_charset($link, 'latin1');
// Return the connection to the pool.
mysqli_close($link);
// Reopen the same persistent connection, which should be reset.
$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);
// Compare the thread IDs to ensure the connection was reused.
if (mysqli_thread_id($link) !== $thread_id) {
printf("[003] Expected the pooled connection to be reused\n");
}
// Check the session variable to ensure it is no longer present.
$v = mysqli_query($link, "SELECT @test_var")->fetch_row()[0];
if ($v !== null) {
printf("[004] User variable should be reset, got %s\n", var_export($v, true));
}
// Check the temporary table to ensure it is no longer present.
if (@mysqli_query($link, "SELECT 1 FROM test_reset_tmp") !== false) {
printf("[005] Temporary table should not exist after reset\n");
}
// Check to see we are no longer in a transaction.
$rows = mysqli_query($link, "SELECT COUNT(*) FROM test_reset_trx")->fetch_row()[0];
if ($rows != 0) {
printf("[006] Transaction should have been rolled back, found %d row(s)\n", $rows);
}
// A charset set after connecting is not part of the config, so it is not restored.
if (mysqli_character_set_name($link) !== $default) {
printf("[007] Charset should be reset to %s, got %s\n", $default, mysqli_character_set_name($link));
}
// mysqlnd's cached charset (used for escaping) must still agree with the server.
$server = mysqli_query($link, "SELECT @@session.character_set_connection")->fetch_row()[0];
if (mysqli_character_set_name($link) !== $server) {
printf("[008] Client charset %s disagrees with server %s\n", mysqli_character_set_name($link), $server);
}
// Clean up the non-temporary table.
mysqli_query($link, "DROP TABLE IF EXISTS test_reset_trx");
mysqli_close($link);
echo "done!";
?>
--EXPECT--
done!
--TEST--
mysqli: reusing a persistent connection should reset its session state (COM_RESET_CONNECTION)
--EXTENSIONS--
mysqli
--SKIPIF--
<?php
require_once 'skipifconnectfailure.inc';
?>
--FILE--
<?php
require_once 'connect.inc';
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$host = 'p:' . $host;
$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);
$connection_id = $link->query("SELECT CONNECTION_ID()")->fetch_column();
// Pick a charset the connection does not already use, so the change is observable.
$default_charset = $link->character_set_name();
$charset = $default_charset === 'latin1' ? 'utf8mb4' : 'latin1';
$link->set_charset($charset);
$link->query("SET @test_var = 42");
$link->query("CREATE TEMPORARY TABLE test_reset_tmp (id INT)");
$link->query("DROP TABLE IF EXISTS test_reset_trx");
$link->query("CREATE TABLE test_reset_trx (id INT) ENGINE=InnoDB");
$link->begin_transaction();
$link->query("INSERT INTO test_reset_trx VALUES (1)");
// Return the connection to the pool.
$link->close();
// Reopen the same persistent connection, which should be reset.
$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);
if ($link->query("SELECT CONNECTION_ID()")->fetch_column() !== $connection_id) {
printf("[003] Expected the persistent connection to be reused\n");
}
$v = $link->query("SELECT @test_var")->fetch_column();
if ($v !== null) {
printf("[004] User variable should be reset, got %s\n", var_export($v, true));
}
try {
$link->query("SELECT * FROM test_reset_tmp");
printf("[005] Temporary table should have been dropped\n");
} catch (mysqli_sql_exception $e) {
if ($e->getCode() !== 1146) { // ER_NO_SUCH_TABLE
throw $e;
}
}
$rows = $link->query("SELECT COUNT(*) FROM test_reset_trx")->fetch_column();
if ($rows !== 0) {
printf("[006] Transaction should have been rolled back, found %d row(s)\n", $rows);
}
// A charset set after connecting is not part of the config, so it is not restored.
if ($link->character_set_name() !== $default_charset) {
printf("[007] Charset should be reset to %s, got %s\n", $default_charset, $link->character_set_name());
}
// mysqlnd's charset must still agree with the server.
$server = $link->query("SELECT @@session.character_set_connection")->fetch_column();
if ($link->character_set_name() !== $server) {
printf("[008] Client charset %s disagrees with server %s\n", $link->character_set_name(), $server);
}
echo "done!";
?>
--CLEAN--
<?php
require_once 'connect.inc';
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$link = my_mysqli_connect($host, $user, $passwd, $db, $port, $socket);
$link->query("DROP TABLE IF EXISTS test_reset_trx");
?>
--EXPECT--
done!

28 changes: 28 additions & 0 deletions ext/mysqlnd/mysqlnd_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,33 @@ MYSQLND_METHOD(mysqlnd_command, ping)(MYSQLND_CONN_DATA * const conn)
/* }}} */


/* {{{ mysqlnd_command::reset_connection */
static enum_func_status
MYSQLND_METHOD(mysqlnd_command, reset_connection)(MYSQLND_CONN_DATA * const conn)
{
const func_mysqlnd_protocol_payload_decoder_factory__send_command send_command = conn->payload_decoder_factory->m.send_command;
const func_mysqlnd_protocol_payload_decoder_factory__send_command_handle_response send_command_handle_response = conn->payload_decoder_factory->m.send_command_handle_response;
enum_func_status ret = FAIL;

DBG_ENTER("mysqlnd_command::reset_connection");

ret = send_command(conn->payload_decoder_factory, COM_RESET_CONNECTION, NULL, 0, FALSE,
&conn->state,
conn->error_info,
conn->upsert_status,
conn->stats,
conn->m->send_close,
conn);
if (PASS == ret) {
ret = send_command_handle_response(conn->payload_decoder_factory, PROT_OK_PACKET, FALSE, COM_RESET_CONNECTION, FALSE,
conn->error_info, conn->upsert_status, &conn->last_message);
}

DBG_RETURN(ret);
}
/* }}} */


/* {{{ mysqlnd_command::statistics */
static enum_func_status
MYSQLND_METHOD(mysqlnd_command, statistics)(MYSQLND_CONN_DATA * const conn, zend_string ** message)
Expand Down Expand Up @@ -659,4 +686,5 @@ MYSQLND_CLASS_METHODS_START(mysqlnd_command)
MYSQLND_METHOD(mysqlnd_command, stmt_close),
MYSQLND_METHOD(mysqlnd_command, enable_ssl),
MYSQLND_METHOD(mysqlnd_command, handshake),
MYSQLND_METHOD(mysqlnd_command, reset_connection),
MYSQLND_CLASS_METHODS_END;
33 changes: 28 additions & 5 deletions ext/mysqlnd/mysqlnd_connection.c
Original file line number Diff line number Diff line change
Expand Up @@ -354,14 +354,37 @@ MYSQLND_METHOD(mysqlnd_conn_data, set_server_option)(MYSQLND_CONN_DATA * const c


/* {{{ mysqlnd_conn_data::restart_psession */
static void
static enum_func_status
MYSQLND_METHOD(mysqlnd_conn_data, restart_psession)(MYSQLND_CONN_DATA * conn)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

From what I understand, this function didn't do the reconnection before. By you adding it, you are breaking ABI. Extensions using mysqlnd (other than mysqli and pdo_mysql) will now silently get buggy behaviour.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure what you mean by silently buggy behavior, but do you have a suggestion for what we should do otherwise?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Don't modify this method. Add a new method. We need to keep restart_psession exactly as it was so as not to break existing extensions.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

How would this break existing extensions? If it does break them, why is it problematic to break existing extensions? Shouldn't restart_psession do everything that is necessary to restart a persistent session?

I don't know that I feel strongly here, to be clear, but I don't understand why restart_psession shouldn't do exactly that. If you want to add a new method, what do we call it? Teasingly - actually_restart_psession / restart_psession_correctly / restart_psession_v2?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Consider someone has designed PDO_Improved2 and they use it in their closed-source project. Their extension uses mysqlnd calls just like PDO does. It calls restart_psession with the assumption that it does what it always did: pretty much nothing. If you change the signature and the assumption, you could cause their extension to no longer function. PHP promises no ABI breaks in minor versions, so we'd be breaking this promise.

There is technically very little chance that this will cause problems for someone, but we don't know. Big companies keep secrets.

As for the new name, reset_connection should be fine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I just don't like the idea of introducing cruft here - any reasonable person looking at restart_psession would think "this should reset a persistent session" and then they'd discover, "oh, no, it doesn't really, you actually need to call reset_connection."

That said, like I mentioned I don't feel strongly here. If you don't object, I will leave a TODO mentioning that we should merge reset_connection into restart_psession, and delete reset_connection, in PHP 9.0.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree with you, but that's not the point. mysqlnd is a public API, and we cannot just change it however we want.

I have no objections to removing restart_psession in PHP 9.0, but I think the name reset_connection is more fitting considering the name of this PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

PHP promises no ABI breaks in minor versions, so we'd be breaking this promise.

This is false. There are much less guarantees regarding the internal API and breaking changes are commonly happening (just have a look at UPGRADING.INTERNALS). See also https://github.com/php/policies/blob/main/release-process.rst#minor-version-number.

The RFC also was pretty clear in that it would adjust mysqlnd:

This RFC proposes that we use COM_RESET_CONNECTION in PDO and mysqlnd when reusing persistent connections.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You're right. I'm sorry. The ABI may be broken, but it shouldn't be. I still think adding a new function with a better name is better than reusing the old one.

I think the guidance has recently been changed. Previously, it said that only the internal ABI can be broken.

{
DBG_ENTER("mysqlnd_conn_data::restart_psession");
MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_CONNECT_REUSED);
conn->current_result = NULL;
conn->last_message.s = NULL;
DBG_VOID_RETURN;

enum_func_status ret = conn->command->reset_connection(conn);
if (ret == PASS) {
MYSQLND_INC_CONN_STATISTIC(conn->stats, STAT_CONNECT_REUSED);
conn->current_result = NULL;
conn->last_message.s = NULL;

/* COM_RESET_CONNECTION reverts the session charset to the server
* default. A charset requested as a connection option (DSN charset=,
* MYSQLI_SET_CHARSET_NAME) is part of establishing the connection, so
* re-apply it to match a fresh connect. A charset chosen afterwards
* with set_charset() is not restored - the caller reapplies that
* itself - but realign the cached charset (used for escaping) with the
* now-reset connection. */
Comment thread
kamil-tekiela marked this conversation as resolved.
if (conn->options->charset_name) {
ret = conn->m->set_charset(conn, conn->options->charset_name);
} else if (conn->greet_charset) {
conn->charset = conn->greet_charset;
}

/* Re-execute any MYSQL_INIT_COMMAND commands. */
if (ret == PASS) {
ret = conn->m->execute_init_commands(conn);
}
Comment on lines +381 to +384

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am not sure I like this change. This was not like this before IIRC; it's a BC. I guess that makes sense, but considering it's a BC, it would probably be better without it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Leaving it out would be a BC break for PDO; users would have persistent connections that no longer had the state of their INIT commands. Current mysqlnd users already have this due to COM_CHANGE_USER, which is arguably a bug, and we'd "fix" it here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Isn't the point of this RFC to introduce a BC for PDO? Previously, a persistent connection in PDO would be in the state in which it disconnected, but now it should be back to the state it was just after the connection was established (but before the first command was issued).

I do understand that this is kind of wacky, so perhaps we should split it. Leave it as it was for mysqli and from PDO retrigger the init_commands separately after successful restart.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I believe the RFC was scoped to introduce a very particular BC break for PDO - that you would be unable to use persistent connections unless you were using a recent version of MySQL. I do not think it gives us liberty to reset configuration specified at connection time.

I'm not sure how I feel about splitting this, as I think it'd be beneficial to have parity between PDO and mysqlnd. It would also make the code more complicated. That said, it's your (and the other maintainer's) call.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, it's a tough decision. However, what you consider a "fix" isn't necessarily fixing something that's broken.

We have 3 options:

  • apply it in mysqlnd - breaks current mysqli code in a subtle way with no easy fix
  • apply it only in PDO - small PDO break but we are breaking stuff anyway, so it's probably fine. mysqli keeps working as before
  • don't apply it anywhere - breaks PDO more but also is easier to fix for user. mysqli keeps working as before

I am still leaning towards "don't apply it anywhere" as it seems the safest option to me. Consider code that uses "INSERT INTO table" as an INIT command. With this change, PDO will silently add a new row whenever the connection is reset. There is no way to bring back the old behaviour. If we do not reapply the init command on reset, then the code works as it did before. Will they lose the temporary variables/tables etc.? Yes, but then they can move the statement into exec() and all is fixed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm going to name your options (a), (b), and (c) for clarity. I also am assuming that in option (a) you mean that we could apply it in both mysqlnd and PDO.

As you noted the correct answer depends on the definition of "fix". I believe that anything that does not return the connection to the exact same state as a fresh connection is a bug, and anything that introduces this behavior where it didn't exist before is a backwards compatibility break. I also believe that @TimWolla agrees with this, but he can correct me if I'm off the mark. The tests reflect this: if you specify a charset in the DSN, it should persist; If you specify an INIT command, it should execute.

Reflecting on the current state of the world under the above definition:

  • PDO preserves both the DSN character set and INIT command because it doesn't do any sort of resetting at all. It therefore also preserves session variables, transactions, and other related intermediate state, which is considered a bug according to the above definition. The RFC gives us license to reset this intermediate state, but does not give us license to reset the DSN character set nor state of the INIT command. Additionally, while it is technically possible to have an application that relies on the intermediate state, it's purely hypothetical: you would need to have an application that (1) sets up a persistent connection, and (2) somehow does not rely on the intermediate state for fresh connections as it wouldn't be there, and (3) applies intermediate state but only for fresh connections, and (4) somehow relies on the intermediate state only for persistent connections. I would argue that this is not a BC break as no reasonable code would do this, but even so, again, the RFC gives us license to make the change. From now on I will not refer to this as a BC break.
  • mysqlnd preserves the DSN character set but does not preserve the INIT command. This is a bug according to the above definition. I also don't think that re-executing the INIT command counts as a BC break - I don't see how calling INSERT INTO table on reset would actually break anything, realistically. The option is documented by both PDO and MySQL as being "automatically re-executed if reconnection occurs", and so code using it would need to expect that since a reconnection could happen at any time, it's liable to execute multiple times anyways.

Exploring your options based on the above:

  • (a) would preserve the DSN character set and INIT command (thus, BC) for PDO and would fix a bug (not preserving the INIT command) in mysqlnd, and is therefore permissible and desirable.
  • (b) would preserve the DSN character set and INIT command (thus, BC) for PDO, and would leave mysqlnd broken, and is therefore permissible but undesirable from a parity, code complexity, and bug standpoint.
  • (c) would preserve the DSN character set but reset the state of an INIT command (thus, would break BC) for PDO, and would leave myslqnd broken, and is therefore impermissible.

@kamil-tekiela kamil-tekiela Aug 5, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For option (a), I meant only in mysqlnd. No need to apply it twice in PDO and mysqlnd.

I was not aware that the documentation states "Will automatically be re-executed when reconnecting.". I don't know why it says that, but I guess we can use that to allow us to make a decision. It also says that for mysqli, which makes me think it was a copy-paste error in the PDO documentation. The mysqli extension doesn't have a reconnection feature anymore, but when it did, I assume it re-sent the init commands. Resetting a persistent connection is not the same as reconnection, but I guess we can avail of this historical artefact to justify the BC.

Ok then, keep it as you are proposing now. Let's go with option (a) and apply it in mysqlnd. However, this requires an UPGRADING entry explaining this breaking change for mysqli.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think mysqli should be impacted by this. This wasn't part of the RFC so we should do as suggested in #21857 (comment)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I also believe that TimWolla agrees with this, but he can correct me if I'm off the mark.

That is an accurate representation of my opinion on the matter.

}

DBG_RETURN(ret);
}
/* }}} */

Expand Down
4 changes: 3 additions & 1 deletion ext/mysqlnd/mysqlnd_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ typedef enum_func_status (*func_mysqlnd_execute_com_set_option)(MYSQLND_CONN_DAT
typedef enum_func_status (*func_mysqlnd_execute_com_debug)(MYSQLND_CONN_DATA * const conn);
typedef enum_func_status (*func_mysqlnd_execute_com_init_db)(MYSQLND_CONN_DATA * const conn, const MYSQLND_CSTRING db);
typedef enum_func_status (*func_mysqlnd_execute_com_ping)(MYSQLND_CONN_DATA * const conn);
typedef enum_func_status (*func_mysqlnd_execute_com_reset_connection)(MYSQLND_CONN_DATA * const conn);
typedef enum_func_status (*func_mysqlnd_execute_com_statistics)(MYSQLND_CONN_DATA * const conn, zend_string ** message);
typedef enum_func_status (*func_mysqlnd_execute_com_process_kill)(MYSQLND_CONN_DATA * const conn, const unsigned int process_id, const bool read_response);
typedef enum_func_status (*func_mysqlnd_execute_com_refresh)(MYSQLND_CONN_DATA * const conn, const uint8_t options);
Expand Down Expand Up @@ -344,6 +345,7 @@ MYSQLND_CLASS_METHODS_TYPE(mysqlnd_command)
func_mysqlnd_execute_com_stmt_close stmt_close;
func_mysqlnd_execute_com_enable_ssl enable_ssl;
func_mysqlnd_execute_com_handshake handshake;
func_mysqlnd_execute_com_reset_connection reset_connection;
};


Expand Down Expand Up @@ -482,7 +484,7 @@ typedef enum_func_status (*func_mysqlnd_conn_data__free_reference)(MYSQLND_CONN_
typedef enum_func_status (*func_mysqlnd_conn_data__send_command_do_request)(MYSQLND_CONN_DATA * const conn, const enum php_mysqlnd_server_command command, const zend_uchar * const arg, const size_t arg_len, const bool silent, const bool ignore_upsert_status);
typedef enum_func_status (*func_mysqlnd_conn_data__send_command_handle_response)(MYSQLND_CONN_DATA * const conn, const enum mysqlnd_packet_type ok_packet, const bool silent, const enum php_mysqlnd_server_command command, const bool ignore_upsert_status);

typedef void (*func_mysqlnd_conn_data__restart_psession)(MYSQLND_CONN_DATA * conn);
typedef enum_func_status (*func_mysqlnd_conn_data__restart_psession)(MYSQLND_CONN_DATA * conn);
typedef void (*func_mysqlnd_conn_data__end_psession)(MYSQLND_CONN_DATA * conn);
typedef enum_func_status (*func_mysqlnd_conn_data__send_close)(MYSQLND_CONN_DATA * conn);

Expand Down
37 changes: 31 additions & 6 deletions ext/pdo/pdo_dbh.c
Original file line number Diff line number Diff line change
Expand Up @@ -414,12 +414,37 @@ PDO_API void php_pdo_internal_construct_driver(INTERNAL_FUNCTION_PARAMETERS, zen
if (le->type == php_pdo_list_entry()) {
pdbh = (pdo_dbh_t*)le->ptr;

/* is the connection still alive ? */
if (pdbh->methods->check_liveness && FAILURE == (pdbh->methods->check_liveness)(pdbh)) {
/* nope... need to kill it */
pdbh->refcount--;
zend_list_close(le);
pdbh = NULL;
if (pdbh->refcount == 1) {
/* refcount 1 means no other open handle is using this
* pooled connection, so we can reset its server-side
* session state before handing it out. reset_connection
* doubles as a liveness check; drivers without it fall
* back to check_liveness. */
zend_result alive = SUCCESS;
if (pdbh->methods->reset_connection) {
alive = (pdbh->methods->reset_connection)(pdbh);
} else if (pdbh->methods->check_liveness) {
alive = (pdbh->methods->check_liveness)(pdbh);
}
if (FAILURE == alive) {
/* Removing it from the persistent list runs the
* persistent destructor, which closes and frees the
* connection. Only correct because refcount is 1. */
zend_hash_del(&EG(persistent_list), hash_key);
pdbh = NULL;
}
} else {
/* Another open handle shares this connection, so we must
* not reset it: that would discard session state (open
* transactions, temporary tables, user variables) the
* other handle relies on. If it has died, drop our
* reference and stop reusing it; it is closed once the
* last handle referencing it is released. */
if (pdbh->methods->check_liveness && FAILURE == (pdbh->methods->check_liveness)(pdbh)) {
pdbh->refcount--;
zend_list_close(le);
pdbh = NULL;
}
}
}
}
Expand Down
12 changes: 11 additions & 1 deletion ext/pdo/php_pdo_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ struct pdo_bound_param_data;
# define FALSE 0
#endif

#define PDO_DRIVER_API 20240423
#define PDO_DRIVER_API 20260502

/* Doctrine hardcodes these constants, avoid changing their values. */
enum pdo_param_type {
Expand Down Expand Up @@ -268,6 +268,15 @@ typedef int (*pdo_dbh_get_attr_func)(pdo_dbh_t *dbh, zend_long attr, zval *val);
* You may set this handler to NULL, which is equivalent to returning SUCCESS. */
typedef zend_result (*pdo_dbh_check_liveness_func)(pdo_dbh_t *dbh);

/* called when a persistent connection is pulled from the pool for reuse, to
* reset any server-side session state left over from the previous request.
* Return SUCCESS if the connection was reset and is ready for reuse, FAILURE
* otherwise (PDO will then discard it). A successful reset also implies the
* connection is alive, so when this handler is present PDO does not also call
* check_liveness. You may set this handler to NULL, in which case PDO falls
* back to check_liveness. */
typedef zend_result (*pdo_dbh_reset_connection_func)(pdo_dbh_t *dbh);

/* called at request end for each persistent dbh; this gives the driver
* the opportunity to safely release resources that only have per-request
* scope */
Expand Down Expand Up @@ -313,6 +322,7 @@ struct pdo_dbh_methods {
pdo_dbh_txn_func in_transaction;
pdo_dbh_get_gc_func get_gc;
pdo_dbh_sql_scanner scanner;
pdo_dbh_reset_connection_func reset_connection;
};

/* }}} */
Expand Down
Loading
Loading