diff --git a/.github/workflows/release-on-milestone-closed.yml b/.github/workflows/release-on-milestone-closed.yml index 218f39d031d..c02383e0257 100644 --- a/.github/workflows/release-on-milestone-closed.yml +++ b/.github/workflows/release-on-milestone-closed.yml @@ -15,7 +15,7 @@ jobs: uses: "actions/checkout@v3" - name: "Release" - uses: "laminas/automatic-releases@1.0.1" + uses: "laminas/automatic-releases@1.24.0" with: command-name: "laminas:automatic-releases:release" env: diff --git a/composer.json b/composer.json index ad8cc15c538..2ba74c84e29 100644 --- a/composer.json +++ b/composer.json @@ -39,11 +39,12 @@ }, "require-dev": { "doctrine/coding-standard": "11.1.0", + "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2022.3", "phpstan/phpstan": "1.9.14", "phpstan/phpstan-phpunit": "1.3.3", "phpstan/phpstan-strict-rules": "^1.4", - "phpunit/phpunit": "9.6.0", + "phpunit/phpunit": "9.6.3", "psalm/plugin-phpunit": "0.18.4", "squizlabs/php_codesniffer": "3.7.1", "symfony/cache": "^5.4|^6.0", diff --git a/src/Driver/PgSQL/Result.php b/src/Driver/PgSQL/Result.php index 9838f7fc9b2..4737a9664ba 100644 --- a/src/Driver/PgSQL/Result.php +++ b/src/Driver/PgSQL/Result.php @@ -37,6 +37,15 @@ public function __construct(PgSqlResult $result) $this->result = $result; } + public function __destruct() + { + if (! isset($this->result)) { + return; + } + + $this->free(); + } + /** {@inheritdoc} */ public function fetchNumeric(): array|false { diff --git a/tests/Functional/Driver/PgSQL/ResultTest.php b/tests/Functional/Driver/PgSQL/ResultTest.php index 208d2140506..16fe5dbe69d 100644 --- a/tests/Functional/Driver/PgSQL/ResultTest.php +++ b/tests/Functional/Driver/PgSQL/ResultTest.php @@ -4,12 +4,20 @@ namespace Doctrine\DBAL\Tests\Functional\Driver\PgSQL; +use Doctrine\DBAL\Driver\PgSQL\Result; use Doctrine\DBAL\Tests\FunctionalTestCase; use Doctrine\DBAL\Tests\TestUtil; use Doctrine\DBAL\Types\Types; +use Error; use Generator; +use PgSql\Connection as PgSqlConnection; +use function assert; use function chr; +use function pg_query; +use function pg_result_status; + +use const PGSQL_TUPLES_OK; class ResultTest extends FunctionalTestCase { @@ -209,4 +217,21 @@ public function testTypeConversionWithDuplicateFieldNames(): void $this->connection->fetchFirstColumn('SELECT a.*, b.* FROM types_test a, types_test2 b'), ); } + + public function testResultIsFreedOnDestruct(): void + { + $pgsqlConnection = $this->connection->getNativeConnection(); + assert($pgsqlConnection instanceof PgSqlConnection); + $pgsqlResult = pg_query($pgsqlConnection, 'SELECT 1'); + assert($pgsqlResult !== false); + + self::assertSame(PGSQL_TUPLES_OK, pg_result_status($pgsqlResult)); + + new Result($pgsqlResult); + + $this->expectException(Error::class); + $this->expectExceptionMessage('PostgreSQL result has already been closed'); + + pg_result_status($pgsqlResult); + } } diff --git a/tests/Logging/MiddlewareTest.php b/tests/Logging/MiddlewareTest.php index b5b71849358..62db183e266 100644 --- a/tests/Logging/MiddlewareTest.php +++ b/tests/Logging/MiddlewareTest.php @@ -8,16 +8,13 @@ use Doctrine\DBAL\Driver\Connection; use Doctrine\DBAL\Logging\Middleware; use Doctrine\DBAL\ParameterType; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Psr\Log\LoggerInterface; +use Psr\Log\Test\TestLogger; class MiddlewareTest extends TestCase { private Driver $driver; - - /** @var LoggerInterface&MockObject */ - private LoggerInterface $logger; + private TestLogger $logger; public function setUp(): void { @@ -27,7 +24,7 @@ public function setUp(): void $driver->method('connect') ->willReturn($connection); - $this->logger = $this->createMock(LoggerInterface::class); + $this->logger = new TestLogger(); $middleware = new Middleware($this->logger); $this->driver = $middleware->wrap($driver); @@ -35,96 +32,91 @@ public function setUp(): void public function testConnectAndDisconnect(): void { - $this->logger->expects(self::exactly(2)) - ->method('info') - ->withConsecutive( - [ - 'Connecting with parameters {params}', - [ - 'params' => [ - 'username' => 'admin', - 'password' => '', - 'url' => '', - ], - ], - ], - ['Disconnecting', []], - ); - $this->driver->connect([ 'username' => 'admin', 'password' => 'Passw0rd!', 'url' => 'mysql://user:secret@localhost/mydb', ]); + + self::assertTrue($this->logger->hasInfo([ + 'message' => 'Connecting with parameters {params}', + 'context' => [ + 'params' => [ + 'username' => 'admin', + 'password' => '', + 'url' => '', + ], + ], + ])); } public function testQuery(): void { - $this->logger->expects(self::once()) - ->method('debug') - ->with('Executing query: {sql}', ['sql' => 'SELECT 1']); - $connection = $this->driver->connect([]); $connection->query('SELECT 1'); + + self::assertTrue($this->logger->hasDebug([ + 'message' => 'Executing query: {sql}', + 'context' => ['sql' => 'SELECT 1'], + ])); } public function testExec(): void { - $this->logger->expects(self::once()) - ->method('debug') - ->with('Executing statement: {sql}', ['sql' => 'DROP DATABASE doctrine']); - $connection = $this->driver->connect([]); $connection->exec('DROP DATABASE doctrine'); + + self::assertTrue($this->logger->hasDebug([ + 'message' => 'Executing statement: {sql}', + 'context' => ['sql' => 'DROP DATABASE doctrine'], + ])); } public function testBeginCommitRollback(): void { - $this->logger->expects(self::exactly(3)) - ->method('debug') - ->withConsecutive( - ['Beginning transaction'], - ['Committing transaction'], - ['Rolling back transaction'], - ); - $connection = $this->driver->connect([]); $connection->beginTransaction(); $connection->commit(); $connection->rollBack(); + + self::assertTrue($this->logger->hasDebug('Beginning transaction')); + self::assertTrue($this->logger->hasDebug('Committing transaction')); + self::assertTrue($this->logger->hasDebug('Rolling back transaction')); } public function testExecuteStatementWithParameters(): void { - $this->logger->expects(self::once()) - ->method('debug') - ->with('Executing statement: {sql} (parameters: {params}, types: {types})', [ - 'sql' => 'SELECT ?, ?', - 'params' => [1 => 42], - 'types' => [1 => ParameterType::INTEGER], - ]); - $connection = $this->driver->connect([]); $statement = $connection->prepare('SELECT ?, ?'); $statement->bindValue(1, 42, ParameterType::INTEGER); $statement->execute(); + + self::assertTrue($this->logger->hasDebug([ + 'message' => 'Executing statement: {sql} (parameters: {params}, types: {types})', + 'context' => [ + 'sql' => 'SELECT ?, ?', + 'params' => [1 => 42], + 'types' => [1 => ParameterType::INTEGER], + ], + ])); } public function testExecuteStatementWithNamedParameters(): void { - $this->logger->expects(self::once()) - ->method('debug') - ->with('Executing statement: {sql} (parameters: {params}, types: {types})', [ - 'sql' => 'SELECT :value', - 'params' => ['value' => 'Test'], - 'types' => ['value' => ParameterType::STRING], - ]); - $connection = $this->driver->connect([]); $statement = $connection->prepare('SELECT :value'); $statement->bindValue('value', 'Test', ParameterType::STRING); $statement->execute(); + + self::assertTrue($this->logger->hasDebug([ + 'message' => 'Executing statement: {sql} (parameters: {params}, types: {types})', + 'context' => [ + 'sql' => 'SELECT :value', + 'params' => ['value' => 'Test'], + 'types' => ['value' => ParameterType::STRING], + ], + ])); } }