Skip to content

Commit ce82b06

Browse files
authored
Merge pull request #5893 from derrabus/bugfix/deallocate-statements
Deallocate prepared statements in destructor
2 parents 0b23e06 + dbb3db7 commit ce82b06

File tree

4 files changed

+72
-0
lines changed

4 files changed

+72
-0
lines changed

phpstan.neon.dist

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@ parameters:
148148
- '~^Parameter #1 \$row of method Doctrine\\DBAL\\Driver\\PgSQL\\Result\:\:mapAssociativeRow\(\) expects array<string, string\|null>, array<int\|string, string\|null> given\.$~'
149149
- '~^Parameter #1 \$row of method Doctrine\\DBAL\\Driver\\PgSQL\\Result\:\:mapNumericRow\(\) expects array<int, string\|null>, array<int\|string, string\|null> given\.$~'
150150

151+
# Ignore isset() checks in destructors.
152+
- '~^Property Doctrine\\DBAL\\Driver\\PgSQL\\Statement\:\:\$connection \(PgSql\\Connection\|resource\) in isset\(\) is not nullable\.$~'
153+
151154
# On PHP 7.4, pg_fetch_all() might return false for empty result sets.
152155
-
153156
message: '~^Strict comparison using === between array<int, array> and false will always evaluate to false\.$~'

psalm.xml.dist

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,7 @@
646646
<!-- PgSql objects are represented as resources in PHP 7.4 -->
647647
<referencedFunction name="pg_affected_rows"/>
648648
<referencedFunction name="pg_escape_bytea"/>
649+
<referencedFunction name="pg_escape_identifier"/>
649650
<referencedFunction name="pg_escape_literal"/>
650651
<referencedFunction name="pg_fetch_all"/>
651652
<referencedFunction name="pg_fetch_all_columns"/>
@@ -657,6 +658,7 @@
657658
<referencedFunction name="pg_get_result"/>
658659
<referencedFunction name="pg_last_error"/>
659660
<referencedFunction name="pg_num_fields"/>
661+
<referencedFunction name="pg_query"/>
660662
<referencedFunction name="pg_result_error_field"/>
661663
<referencedFunction name="pg_send_execute"/>
662664
<referencedFunction name="pg_send_prepare"/>
@@ -748,6 +750,9 @@
748750
<errorLevel type="suppress">
749751
<!-- Running isset() checks on properties that should be initialized by setUp() is fine. -->
750752
<directory name="tests"/>
753+
754+
<!-- Ignore isset() checks in destructors. -->
755+
<file name="src/Driver/PgSQL/Statement.php"/>
751756
</errorLevel>
752757
</RedundantPropertyInitializationCheck>
753758
<ReferenceConstraintViolation>

src/Driver/PgSQL/Statement.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
use function is_resource;
1919
use function ksort;
2020
use function pg_escape_bytea;
21+
use function pg_escape_identifier;
2122
use function pg_get_result;
2223
use function pg_last_error;
24+
use function pg_query;
2325
use function pg_result_error;
2426
use function pg_send_execute;
2527
use function sprintf;
@@ -59,6 +61,18 @@ public function __construct($connection, string $name, array $parameterMap)
5961
$this->parameterMap = $parameterMap;
6062
}
6163

64+
public function __destruct()
65+
{
66+
if (! isset($this->connection)) {
67+
return;
68+
}
69+
70+
@pg_query(
71+
$this->connection,
72+
'DEALLOCATE ' . pg_escape_identifier($this->connection, $this->name),
73+
);
74+
}
75+
6276
/** {@inheritdoc} */
6377
public function bindValue($param, $value, $type = ParameterType::STRING): bool
6478
{
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\DBAL\Tests\Functional\Driver\PgSQL;
6+
7+
use Doctrine\DBAL\Driver\PgSQL\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 ReflectionProperty;
13+
14+
use function sprintf;
15+
16+
class StatementTest extends FunctionalTestCase
17+
{
18+
protected function setUp(): void
19+
{
20+
parent::setUp();
21+
22+
if (TestUtil::isDriverOneOf('pgsql')) {
23+
return;
24+
}
25+
26+
self::markTestSkipped('This test requires the pgsql driver.');
27+
}
28+
29+
public function testStatementsAreDeallocatedProperly(): void
30+
{
31+
$statement = $this->connection->prepare('SELECT 1');
32+
33+
$property = new ReflectionProperty(WrapperStatement::class, 'stmt');
34+
$property->setAccessible(true);
35+
36+
$driverStatement = $property->getValue($statement);
37+
38+
$property = new ReflectionProperty(Statement::class, 'name');
39+
$property->setAccessible(true);
40+
41+
$name = $property->getValue($driverStatement);
42+
43+
unset($statement, $driverStatement);
44+
45+
$this->expectException(DriverException::class);
46+
$this->expectExceptionMessageMatches('/prepared statement .* does not exist/');
47+
48+
$this->connection->executeQuery(sprintf('EXECUTE "%s"', $name));
49+
}
50+
}

0 commit comments

Comments
 (0)