Skip to content

Commit c3b7675

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 c3b7675

File tree

3 files changed

+61
-2
lines changed

3 files changed

+61
-2
lines changed

src/Driver/Mysqli/Result.php

Lines changed: 4 additions & 1 deletion
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;

src/Driver/Mysqli/Statement.php

Lines changed: 6 additions & 1 deletion
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
/**
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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 mysqli_sql_exception;
13+
use PHPUnit\Framework\Attributes\RequiresPhpExtension;
14+
use ReflectionProperty;
15+
16+
#[RequiresPhpExtension('mysqli')]
17+
class StatementTest extends FunctionalTestCase
18+
{
19+
protected function setUp(): void
20+
{
21+
parent::setUp();
22+
23+
if (TestUtil::isDriverOneOf('mysqli')) {
24+
return;
25+
}
26+
27+
self::markTestSkipped('This test requires the mysqli driver.');
28+
}
29+
30+
public function testStatementsAreDeallocatedProperly(): void
31+
{
32+
$statement = $this->connection->prepare('SELECT 1');
33+
34+
$property = new ReflectionProperty(WrapperStatement::class, 'stmt');
35+
$property->setAccessible(true);
36+
37+
$driverStatement = $property->getValue($statement);
38+
39+
$mysqliSProperty = new ReflectionProperty(Statement::class, 'stmt');
40+
$mysqliSProperty->setAccessible(true);
41+
42+
$mysqliStatement = $mysqliSProperty->getValue($driverStatement);
43+
44+
unset($statement, $driverStatement);
45+
46+
// $this->expectException(mysqli_sql_exception::class);
47+
// $this->expectExceptionMessage('mysqli_stmt object is already closed');
48+
49+
$mysqliStatement->execute();
50+
}
51+
}

0 commit comments

Comments
 (0)