From 7a755ccb859cf14da29b943f909702b08a43c2d3 Mon Sep 17 00:00:00 2001 From: Peter Mein Date: Thu, 2 Jan 2025 16:43:01 +0100 Subject: [PATCH] bugfix: deallocate mysqli prepared statement Long running processes might hit the `max_prepared_stmt_count` due not deallocating the statement correctly. --- src/Driver/Mysqli/Result.php | 7 ++- src/Driver/Mysqli/Statement.php | 7 ++- .../Driver/Mysqli/StatementTest.php | 52 +++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 tests/Functional/Driver/Mysqli/StatementTest.php diff --git a/src/Driver/Mysqli/Result.php b/src/Driver/Mysqli/Result.php index cb70940be20..b08adb8c6f1 100644 --- a/src/Driver/Mysqli/Result.php +++ b/src/Driver/Mysqli/Result.php @@ -39,9 +39,14 @@ final class Result implements ResultInterface /** * @internal The result can be only instantiated by its driver connection or statement. * + * @param Statement|null $statementReference This reference is to not trigger destruction of the Statement object too early. + * * @throws Exception */ - public function __construct(private readonly mysqli_stmt $statement) + public function __construct( + private readonly mysqli_stmt $statement, + private readonly ?Statement $statementReference = null, + ) { $meta = $statement->result_metadata(); $this->hasColumns = $meta !== false; diff --git a/src/Driver/Mysqli/Statement.php b/src/Driver/Mysqli/Statement.php index 8436fadfc6c..9a2151786e9 100644 --- a/src/Driver/Mysqli/Statement.php +++ b/src/Driver/Mysqli/Statement.php @@ -49,6 +49,11 @@ public function __construct(private readonly mysqli_stmt $stmt) $this->boundValues = array_fill(1, $paramCount, null); } + public function __destruct() + { + @$this->stmt->close(); + } + public function bindValue(int|string $param, mixed $value, ParameterType $type): void { assert(is_int($param)); @@ -72,7 +77,7 @@ public function execute(): Result throw StatementError::upcast($e); } - return new Result($this->stmt); + return new Result($this->stmt, $this); } /** diff --git a/tests/Functional/Driver/Mysqli/StatementTest.php b/tests/Functional/Driver/Mysqli/StatementTest.php new file mode 100644 index 00000000000..c4680d33db1 --- /dev/null +++ b/tests/Functional/Driver/Mysqli/StatementTest.php @@ -0,0 +1,52 @@ +connection->prepare('SELECT 1'); + + $property = new ReflectionProperty(WrapperStatement::class, 'stmt'); + $property->setAccessible(true); + + $driverStatement = $property->getValue($statement); + + $mysqliProperty = new ReflectionProperty(Statement::class, 'stmt'); + $mysqliProperty->setAccessible(true); + + $mysqliStatement = $mysqliProperty->getValue($driverStatement); + + unset($statement, $driverStatement); + + $this->expectException(Error::class); + $this->expectExceptionMessage('mysqli_stmt object is already closed'); + + $mysqliStatement->execute(); + } +}