From 28fb97b9a58ed1442120a93e0f99e1fe4509c649 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:11:38 +0000 Subject: [PATCH 1/4] Initial plan From 6617df14974bdee666d50ffe98f702dca8079b64 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:19:12 +0000 Subject: [PATCH 2/4] feat: add NoDiscard to result-oriented API methods Agent-Logs-Url: https://github.com/nextras/dbal/sessions/05046898-4e07-4b53-91cf-3c4a037b94a7 Co-authored-by: hrach <284263+hrach@users.noreply.github.com> --- src/Connection.php | 8 ++ src/IConnection.php | 8 ++ src/Platforms/Data/Fqn.php | 1 + src/Platforms/IPlatform.php | 17 ++++ src/Result/Result.php | 6 ++ src/Result/Row.php | 2 + tests/cases/unit/NoDiscardAttributeTest.php | 100 ++++++++++++++++++++ 7 files changed, 142 insertions(+) create mode 100644 tests/cases/unit/NoDiscardAttributeTest.php diff --git a/src/Connection.php b/src/Connection.php index 9909c8ac..ac790113 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -93,6 +93,7 @@ public function reconnectWithConfig(array $config): void } + #[\NoDiscard] public function getDriver(): IDriver { return $this->driver; @@ -100,6 +101,7 @@ public function getDriver(): IDriver /** @inheritdoc */ + #[\NoDiscard] public function getConfig(): array { return $this->config; @@ -144,6 +146,7 @@ public function queryByQueryBuilder(QueryBuilder $queryBuilder): Result /** @inheritdoc */ + #[\NoDiscard] public function getLastInsertedId(string|Fqn|null $sequenceName = null) { if (!$this->connected) { @@ -154,6 +157,7 @@ public function getLastInsertedId(string|Fqn|null $sequenceName = null) /** @inheritdoc */ + #[\NoDiscard] public function getAffectedRows(): int { if (!$this->connected) { @@ -164,6 +168,7 @@ public function getAffectedRows(): int /** @inheritdoc */ + #[\NoDiscard] public function getPlatform(): IPlatform { if ($this->platform === null) { @@ -175,6 +180,7 @@ public function getPlatform(): IPlatform /** @inheritdoc */ + #[\NoDiscard] public function createQueryBuilder(): QueryBuilder { return new QueryBuilder($this->getPlatform()); @@ -254,6 +260,7 @@ public function rollbackTransaction(): void /** @inheritdoc */ + #[\NoDiscard] public function getTransactionNestedIndex(): int { return $this->nestedTransactionIndex; @@ -291,6 +298,7 @@ public function rollbackSavepoint(string $name): void /** @inheritdoc */ + #[\NoDiscard] public function ping(): bool { if (!$this->connected) { diff --git a/src/IConnection.php b/src/IConnection.php index 144c4586..7c266fdd 100644 --- a/src/IConnection.php +++ b/src/IConnection.php @@ -50,6 +50,7 @@ public function reconnect(): void; public function reconnectWithConfig(array $config): void; + #[\NoDiscard] public function getDriver(): IDriver; @@ -57,6 +58,7 @@ public function getDriver(): IDriver; * Returns connection configuration. * @return array */ + #[\NoDiscard] public function getConfig(): array; @@ -103,18 +105,22 @@ public function queryByQueryBuilder(QueryBuilder $queryBuilder): Result; * * @return int|string|null */ + #[\NoDiscard] public function getLastInsertedId(string|Fqn|null $sequenceName = null); /** * Returns number of affected rows. */ + #[\NoDiscard] public function getAffectedRows(): int; + #[\NoDiscard] public function getPlatform(): IPlatform; + #[\NoDiscard] public function createQueryBuilder(): QueryBuilder; @@ -158,6 +164,7 @@ public function rollbackTransaction(): void; * 1 = basic transaction * >1 = nested transaction through save-points */ + #[\NoDiscard] public function getTransactionNestedIndex(): int; @@ -189,6 +196,7 @@ public function rollbackSavepoint(string $name): void; * $connection->reconnect(); * } */ + #[\NoDiscard] public function ping(): bool; diff --git a/src/Platforms/Data/Fqn.php b/src/Platforms/Data/Fqn.php index 9a52b42d..f07de066 100644 --- a/src/Platforms/Data/Fqn.php +++ b/src/Platforms/Data/Fqn.php @@ -22,6 +22,7 @@ public function __construct( } + #[\NoDiscard] public function getUnescaped(): string { if ($this->schema === '') { diff --git a/src/Platforms/IPlatform.php b/src/Platforms/IPlatform.php index d2ec2fd3..a4309a6d 100644 --- a/src/Platforms/IPlatform.php +++ b/src/Platforms/IPlatform.php @@ -22,6 +22,7 @@ interface IPlatform /** * Returns platform name. */ + #[\NoDiscard] public function getName(): string; @@ -30,6 +31,7 @@ public function getName(): string; * If no schema is provided, it uses the current schema name (search path). * @return array */ + #[\NoDiscard] public function getTables(?string $schema = null): array; @@ -37,6 +39,7 @@ public function getTables(?string $schema = null): array; * Returns list of table columns metadata, indexed by column name. * @return array */ + #[\NoDiscard] public function getColumns(string $table, ?string $schema = null): array; @@ -44,6 +47,7 @@ public function getColumns(string $table, ?string $schema = null): array; * Returns list of table foreign keys, indexed by column name. * @return array */ + #[\NoDiscard] public function getForeignKeys(string $table, ?string $schema = null): array; @@ -51,12 +55,14 @@ public function getForeignKeys(string $table, ?string $schema = null): array; * Returns primary sequence name for the table. * If not supported nor present, returns a null. */ + #[\NoDiscard] public function getPrimarySequenceName(string $table, ?string $schema = null): ?string; /** * Formats string value to SQL string. */ + #[\NoDiscard] public function formatString(string $value): string; @@ -65,60 +71,70 @@ public function formatString(string $value): string; * @param int $mode -1 = left, 0 = both, 1 = right * @return mixed */ + #[\NoDiscard] public function formatStringLike(string $value, int $mode); /** * Formats Json value to SQL string. */ + #[\NoDiscard] public function formatJson(mixed $value): string; /** * Formats boolean to SQL string. */ + #[\NoDiscard] public function formatBool(bool $value): string; /** * Formats column or dot separated identifier to SQL string. */ + #[\NoDiscard] public function formatIdentifier(string $value): string; /** * Formats time-zone aware DateTimeInterface instance to SQL string. */ + #[\NoDiscard] public function formatDateTime(DateTimeInterface $value): string; /** * Formats local DateTimeInterface instance to SQL string. */ + #[\NoDiscard] public function formatLocalDateTime(DateTimeInterface $value): string; /** * Formats local date from DateTimeInterface instance to SQL string. */ + #[\NoDiscard] public function formatLocalDate(DateTimeInterface $value): string; /** * Formats DateInterval to SQL string. */ + #[\NoDiscard] public function formatDateInterval(DateInterval $value): string; /** * Formats blob value to SQL string. */ + #[\NoDiscard] public function formatBlob(string $value): string; /** * Formats LIMIT & OFFSET values to SQL string. */ + #[\NoDiscard] public function formatLimitOffset(?int $limit, ?int $offset): string; @@ -126,6 +142,7 @@ public function formatLimitOffset(?int $limit, ?int $offset): string; * Returns SQL file parser * !!!This function requires nextras/multi-query-parser dependency!!! */ + #[\NoDiscard] public function createMultiQueryParser(): IMultiQueryParser; diff --git a/src/Result/Result.php b/src/Result/Result.php index 000b645b..0c7c8281 100644 --- a/src/Result/Result.php +++ b/src/Result/Result.php @@ -59,12 +59,14 @@ public function unbuffered(): Result } + #[\NoDiscard] public function getAdapter(): IResultAdapter { return $this->adapter; } + #[\NoDiscard] public function fetch(): ?Row { $data = $this->adapter->fetch(); @@ -111,6 +113,7 @@ private function normalize(array &$data): void /** * @return mixed|null */ + #[\NoDiscard] public function fetchField(int $column = 0) { if (($row = $this->fetch()) !== null) { // = intentionally @@ -124,6 +127,7 @@ public function fetchField(int $column = 0) /** * @return Row[] */ + #[\NoDiscard] public function fetchAll(): array { return iterator_to_array($this); @@ -133,6 +137,7 @@ public function fetchAll(): array /** * @return array */ + #[\NoDiscard] public function fetchPairs(?string $key = null, ?string $value = null): array { if ($key === null && $value === null) { @@ -164,6 +169,7 @@ public function fetchPairs(?string $key = null, ?string $value = null): array * Returns list of column names in result. * @return list */ + #[\NoDiscard] public function getColumns(): array { return array_map( diff --git a/src/Result/Row.php b/src/Result/Row.php index 7c1fc44a..44a2fba9 100644 --- a/src/Result/Row.php +++ b/src/Result/Row.php @@ -23,6 +23,7 @@ public function __construct(array $data) /** * @return array */ + #[\NoDiscard] public function toArray(): array { return (array) $this; @@ -36,6 +37,7 @@ public function __get(string $name): mixed } + #[\NoDiscard] public function getNthField(int $offset): mixed { $slice = array_slice((array) $this, $offset, 1); diff --git a/tests/cases/unit/NoDiscardAttributeTest.php b/tests/cases/unit/NoDiscardAttributeTest.php new file mode 100644 index 00000000..792fe340 --- /dev/null +++ b/tests/cases/unit/NoDiscardAttributeTest.php @@ -0,0 +1,100 @@ +assertMethodHasNoDiscard(IConnection::class, 'getDriver'); + $this->assertMethodHasNoDiscard(IConnection::class, 'getConfig'); + $this->assertMethodHasNoDiscard(IConnection::class, 'getLastInsertedId'); + $this->assertMethodHasNoDiscard(IConnection::class, 'getAffectedRows'); + $this->assertMethodHasNoDiscard(IConnection::class, 'getPlatform'); + $this->assertMethodHasNoDiscard(IConnection::class, 'createQueryBuilder'); + $this->assertMethodHasNoDiscard(IConnection::class, 'getTransactionNestedIndex'); + $this->assertMethodHasNoDiscard(IConnection::class, 'ping'); + } + + + public function testConnectionMethodsHaveNoDiscard(): void + { + $this->assertMethodHasNoDiscard(Connection::class, 'getDriver'); + $this->assertMethodHasNoDiscard(Connection::class, 'getConfig'); + $this->assertMethodHasNoDiscard(Connection::class, 'getLastInsertedId'); + $this->assertMethodHasNoDiscard(Connection::class, 'getAffectedRows'); + $this->assertMethodHasNoDiscard(Connection::class, 'getPlatform'); + $this->assertMethodHasNoDiscard(Connection::class, 'createQueryBuilder'); + $this->assertMethodHasNoDiscard(Connection::class, 'getTransactionNestedIndex'); + $this->assertMethodHasNoDiscard(Connection::class, 'ping'); + } + + + public function testPlatformInterfaceMethodsHaveNoDiscard(): void + { + $this->assertMethodHasNoDiscard(IPlatform::class, 'getName'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'getTables'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'getColumns'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'getForeignKeys'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'getPrimarySequenceName'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'formatString'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'formatStringLike'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'formatJson'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'formatBool'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'formatIdentifier'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'formatDateTime'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'formatLocalDateTime'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'formatLocalDate'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'formatDateInterval'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'formatBlob'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'formatLimitOffset'); + $this->assertMethodHasNoDiscard(IPlatform::class, 'createMultiQueryParser'); + } + + + public function testResultMethodsHaveNoDiscard(): void + { + $this->assertMethodHasNoDiscard(Result::class, 'getAdapter'); + $this->assertMethodHasNoDiscard(Result::class, 'fetch'); + $this->assertMethodHasNoDiscard(Result::class, 'fetchField'); + $this->assertMethodHasNoDiscard(Result::class, 'fetchAll'); + $this->assertMethodHasNoDiscard(Result::class, 'fetchPairs'); + $this->assertMethodHasNoDiscard(Result::class, 'getColumns'); + } + + + public function testRowAndFqnMethodsHaveNoDiscard(): void + { + $this->assertMethodHasNoDiscard(Row::class, 'toArray'); + $this->assertMethodHasNoDiscard(Row::class, 'getNthField'); + $this->assertMethodHasNoDiscard(Fqn::class, 'getUnescaped'); + } + + + private function assertMethodHasNoDiscard(string $className, string $methodName): void + { + $method = new ReflectionMethod($className, $methodName); + Assert::same(['NoDiscard'], array_map( + static fn($attribute): string => $attribute->getName(), + $method->getAttributes(), + )); + } +} + + +$test = new NoDiscardAttributeTest(); +$test->run(); From b09022281ba9768ab24949e7d06ac240d27b8197 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Apr 2026 21:07:30 +0000 Subject: [PATCH 3/4] fix: drop NoDiscard from fetch and ping Agent-Logs-Url: https://github.com/nextras/dbal/sessions/ca1d7294-6f6f-459d-be6f-4d7b8ff148f1 Co-authored-by: hrach <284263+hrach@users.noreply.github.com> --- src/Connection.php | 1 - src/IConnection.php | 1 - src/Result/Result.php | 1 - tests/cases/unit/NoDiscardAttributeTest.php | 5 +---- 4 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Connection.php b/src/Connection.php index ac790113..14f02c46 100644 --- a/src/Connection.php +++ b/src/Connection.php @@ -298,7 +298,6 @@ public function rollbackSavepoint(string $name): void /** @inheritdoc */ - #[\NoDiscard] public function ping(): bool { if (!$this->connected) { diff --git a/src/IConnection.php b/src/IConnection.php index 7c266fdd..f9a47aea 100644 --- a/src/IConnection.php +++ b/src/IConnection.php @@ -196,7 +196,6 @@ public function rollbackSavepoint(string $name): void; * $connection->reconnect(); * } */ - #[\NoDiscard] public function ping(): bool; diff --git a/src/Result/Result.php b/src/Result/Result.php index 0c7c8281..26a7eef5 100644 --- a/src/Result/Result.php +++ b/src/Result/Result.php @@ -66,7 +66,6 @@ public function getAdapter(): IResultAdapter } - #[\NoDiscard] public function fetch(): ?Row { $data = $this->adapter->fetch(); diff --git a/tests/cases/unit/NoDiscardAttributeTest.php b/tests/cases/unit/NoDiscardAttributeTest.php index 792fe340..a92626f4 100644 --- a/tests/cases/unit/NoDiscardAttributeTest.php +++ b/tests/cases/unit/NoDiscardAttributeTest.php @@ -27,7 +27,6 @@ public function testConnectionInterfaceMethodsHaveNoDiscard(): void $this->assertMethodHasNoDiscard(IConnection::class, 'getPlatform'); $this->assertMethodHasNoDiscard(IConnection::class, 'createQueryBuilder'); $this->assertMethodHasNoDiscard(IConnection::class, 'getTransactionNestedIndex'); - $this->assertMethodHasNoDiscard(IConnection::class, 'ping'); } @@ -40,7 +39,6 @@ public function testConnectionMethodsHaveNoDiscard(): void $this->assertMethodHasNoDiscard(Connection::class, 'getPlatform'); $this->assertMethodHasNoDiscard(Connection::class, 'createQueryBuilder'); $this->assertMethodHasNoDiscard(Connection::class, 'getTransactionNestedIndex'); - $this->assertMethodHasNoDiscard(Connection::class, 'ping'); } @@ -69,7 +67,6 @@ public function testPlatformInterfaceMethodsHaveNoDiscard(): void public function testResultMethodsHaveNoDiscard(): void { $this->assertMethodHasNoDiscard(Result::class, 'getAdapter'); - $this->assertMethodHasNoDiscard(Result::class, 'fetch'); $this->assertMethodHasNoDiscard(Result::class, 'fetchField'); $this->assertMethodHasNoDiscard(Result::class, 'fetchAll'); $this->assertMethodHasNoDiscard(Result::class, 'fetchPairs'); @@ -88,7 +85,7 @@ public function testRowAndFqnMethodsHaveNoDiscard(): void private function assertMethodHasNoDiscard(string $className, string $methodName): void { $method = new ReflectionMethod($className, $methodName); - Assert::same(['NoDiscard'], array_map( + Assert::same([\NoDiscard::class], array_map( static fn($attribute): string => $attribute->getName(), $method->getAttributes(), )); From f370b68a668d3952d0a5bb622b3d0918c3e1c3e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Apr 2026 16:51:49 +0000 Subject: [PATCH 4/4] fix: add \$_ = to suppress NoDiscard warnings in exception-testing closures Agent-Logs-Url: https://github.com/nextras/dbal/sessions/6a0b1d49-81e5-416c-b5b6-977208455edb Co-authored-by: hrach <284263+hrach@users.noreply.github.com> --- tests/cases/integration/connection.postgres.phpt | 2 +- tests/cases/integration/result.buffering.phpt | 2 +- tests/cases/unit/ResultTest.phpt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/cases/integration/connection.postgres.phpt b/tests/cases/integration/connection.postgres.phpt index 68c7ae8d..871ca37e 100644 --- a/tests/cases/integration/connection.postgres.phpt +++ b/tests/cases/integration/connection.postgres.phpt @@ -38,7 +38,7 @@ class ConnectionPostgresTest extends IntegrationTestCase Assert::same(2, $this->connection->getLastInsertedId(new Fqn(schema: 'public', name: 'publishers_id_seq'))); Assert::exception(function() { - $this->connection->getLastInsertedId(); + $_ = $this->connection->getLastInsertedId(); }, InvalidArgumentException::class, 'PgsqlDriver requires passing a sequence name for getLastInsertedId() method.'); } diff --git a/tests/cases/integration/result.buffering.phpt b/tests/cases/integration/result.buffering.phpt index 21ae2323..15c54257 100644 --- a/tests/cases/integration/result.buffering.phpt +++ b/tests/cases/integration/result.buffering.phpt @@ -36,7 +36,7 @@ class ResultBufferingIntegrationTest extends IntegrationTestCase $unbuffered = $this->connection->query('SELECT * FROM books ORDER BY id')->buffered()->unbuffered(); Assert::same([1, 2, 3, 4], $unbuffered->fetchPairs(null, 'id')); Assert::throws(function () use ($unbuffered): void { - $unbuffered->fetchPairs(null, 'id'); + $_ = $unbuffered->fetchPairs(null, 'id'); }, NotSupportedException::class); $lateChanged = $this->connection->query('SELECT * FROM books ORDER BY id')->buffered(); diff --git a/tests/cases/unit/ResultTest.phpt b/tests/cases/unit/ResultTest.phpt index f9224fff..a09fa198 100644 --- a/tests/cases/unit/ResultTest.phpt +++ b/tests/cases/unit/ResultTest.phpt @@ -60,7 +60,7 @@ class ResultTest extends TestCase Assert::same('First', $result->fetchField()); Assert::same('Two', $result->fetchField(1)); Assert::throws(function () use ($result) { - $result->fetchField(2); + $_ = $result->fetchField(2); }, InvalidArgumentException::class); $adapter = Mockery::mock(IResultAdapter::class); @@ -156,7 +156,7 @@ class ResultTest extends TestCase $adapter->shouldReceive('getNormalizers')->once()->andReturn([]); $result = new Result($adapter); $result->setValueNormalization(false); - $result->fetchPairs(); + $_ = $result->fetchPairs(); }, InvalidArgumentException::class, 'Result::fetchPairs() requires defined key or value.'); }