Skip to content

Commit d92e954

Browse files
committed
bugfix: deallocate mysqli prepared statement
Long running processes might hit the `max_prepared_stmt_count` due not deallocating the statement correctly.
1 parent 6428f1a commit d92e954

File tree

3 files changed

+62
-2
lines changed

3 files changed

+62
-2
lines changed

Diff for: src/Driver/Mysqli/Result.php

+4-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ final class Result implements ResultInterface
4141
*
4242
* @throws Exception
4343
*/
44-
public function __construct(private readonly mysqli_stmt $statement)
44+
public function __construct(
45+
private readonly mysqli_stmt $statement,
46+
private readonly ?Statement $statementReference = null,
47+
)
4548
{
4649
$meta = $statement->result_metadata();
4750
$this->hasColumns = $meta !== false;

Diff for: src/Driver/Mysqli/Statement.php

+6-1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ public function __construct(private readonly mysqli_stmt $stmt)
4949
$this->boundValues = array_fill(1, $paramCount, null);
5050
}
5151

52+
public function __destruct()
53+
{
54+
$this->stmt->close();
55+
}
56+
5257
public function bindValue(int|string $param, mixed $value, ParameterType $type): void
5358
{
5459
assert(is_int($param));
@@ -72,7 +77,7 @@ public function execute(): Result
7277
throw StatementError::upcast($e);
7378
}
7479

75-
return new Result($this->stmt);
80+
return new Result($this->stmt, $this);
7681
}
7782

7883
/**

Diff for: tests/Functional/Driver/Mysqli/StatementTest.php

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\DBAL\Tests\Functional\Driver\Mysqli;
6+
7+
use Doctrine\DBAL\Driver\Mysqli\Statement;
8+
use Doctrine\DBAL\Exception\DriverException;
9+
use Doctrine\DBAL\Statement as WrapperStatement;
10+
use Doctrine\DBAL\Tests\FunctionalTestCase;
11+
use Doctrine\DBAL\Tests\TestUtil;
12+
use Error;
13+
use mysqli_sql_exception;
14+
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
15+
use ReflectionProperty;
16+
17+
#[RequiresPhpExtension('mysqli')]
18+
class StatementTest extends FunctionalTestCase
19+
{
20+
protected function setUp(): void
21+
{
22+
parent::setUp();
23+
24+
if (TestUtil::isDriverOneOf('mysqli')) {
25+
return;
26+
}
27+
28+
self::markTestSkipped('This test requires the mysqli driver.');
29+
}
30+
31+
public function testStatementsAreDeallocatedProperly(): void
32+
{
33+
$statement = $this->connection->prepare('SELECT 1');
34+
35+
$property = new ReflectionProperty(WrapperStatement::class, 'stmt');
36+
$property->setAccessible(true);
37+
38+
$driverStatement = $property->getValue($statement);
39+
40+
$mysqliSProperty = new ReflectionProperty(Statement::class, 'stmt');
41+
$mysqliSProperty->setAccessible(true);
42+
43+
$mysqliStatement = $mysqliSProperty->getValue($driverStatement);
44+
45+
unset($statement, $driverStatement);
46+
47+
$this->expectException(Error::class);
48+
$this->expectExceptionMessage('mysqli_stmt object is already closed');
49+
50+
$mysqliStatement->execute();
51+
}
52+
}

0 commit comments

Comments
 (0)