Skip to content

Commit 88681e7

Browse files
committed
Implement Result::getQueryResult()
php-db/phpdb#172 added getQueryResult() to Driver\ResultInterface, so Pgsql\Result no longer satisfied the contract and loading the class was a fatal before any test could run. Follows Pdo\Result upstream: guard on isQueryResult(), clone the given prototype or a default ResultSet, and initialize it from this result. php-db/phpdb 0.6.x-dev requires php ^8.3, so the php constraint, config.platform.php and the PhpStan check in .laminas-ci.json move to 8.3 — the lock cannot pick up the interface change otherwise. Covered by unit tests over a ResultStub that answers the two methods reading the pgsql resource, and by integration tests running a real select and a fields-less statement through the native driver.
1 parent f077bee commit 88681e7

7 files changed

Lines changed: 180 additions & 27 deletions

File tree

‎.laminas-ci.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{
44
"name": "PhpStan",
55
"job": {
6-
"php": "8.2",
6+
"php": "8.3",
77
"dependencies": "latest",
88
"command": "composer require --dev phpstan/phpstan && vendor/bin/phpstan analyse"
99
}

‎composer.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"config": {
1919
"sort-packages": true,
2020
"platform": {
21-
"php": "8.2.99"
21+
"php": "8.3.99"
2222
},
2323
"allow-plugins": {
2424
"dealerdirect/phpcodesniffer-composer-installer": true
@@ -30,7 +30,7 @@
3030
}
3131
},
3232
"require": {
33-
"php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
33+
"php": "~8.3.0 || ~8.4.0 || ~8.5.0",
3434
"php-db/phpdb": "^0.6.0"
3535
},
3636
"require-dev": {

‎composer.lock‎

Lines changed: 22 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/Result.php‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
use Override;
88
use PgSql\Result as PgSqlResult;
99
use PhpDb\Adapter\Driver\ResultInterface;
10+
use PhpDb\Adapter\Exception;
11+
use PhpDb\ResultSet\ResultSet;
12+
use PhpDb\ResultSet\ResultSetInterface;
1013

1114
use function pg_affected_rows;
1215
use function pg_fetch_assoc;
@@ -94,6 +97,26 @@ public function getGeneratedValue(): int|string|false|null
9497
return $this->generatedValue;
9598
}
9699

100+
/**
101+
* @throws Exception\RuntimeException When this result is not a query result.
102+
*/
103+
#[Override]
104+
public function getQueryResult(?ResultSetInterface $resultPrototype = null): ResultSetInterface
105+
{
106+
if (! $this->isQueryResult()) {
107+
throw new Exception\RuntimeException(
108+
'Cannot produce a query result set from a result that is not a query result;'
109+
. ' check isQueryResult() first'
110+
);
111+
}
112+
113+
$resultPrototype ??= new ResultSet();
114+
$resultSet = clone $resultPrototype;
115+
$resultSet->initialize($this);
116+
117+
return $resultSet;
118+
}
119+
97120
/**
98121
* Get resource
99122
*/

‎test/asset/ResultStub.php‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpDbTestAsset\Pgsql;
6+
7+
use Override;
8+
use PhpDb\Pgsql\Result;
9+
10+
/**
11+
* Result::getQueryResult() only needs isQueryResult() and getFieldCount() to be
12+
* answerable, both of which read the pgsql resource. Overriding them keeps the
13+
* method unit-testable without a live connection.
14+
*/
15+
final class ResultStub extends Result
16+
{
17+
public function __construct(private bool $isQueryResult, private int $fieldCount = 0)
18+
{
19+
}
20+
21+
#[Override]
22+
public function isQueryResult(): bool
23+
{
24+
return $this->isQueryResult;
25+
}
26+
27+
#[Override]
28+
public function getFieldCount(): int
29+
{
30+
return $this->fieldCount;
31+
}
32+
}

‎test/integration/ResultTest.php‎

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpDbIntegrationTest\Pgsql;
6+
7+
use PhpDb\Adapter\Exception;
8+
use PhpDb\Pgsql\Result;
9+
use PhpDbTestAsset\Pgsql\SetupTrait;
10+
use PHPUnit\Framework\Attributes\CoversMethod;
11+
use PHPUnit\Framework\TestCase;
12+
13+
#[CoversMethod(Result::class, 'getQueryResult')]
14+
#[CoversMethod(Result::class, 'isQueryResult')]
15+
class ResultTest extends TestCase
16+
{
17+
use SetupTrait;
18+
19+
public function testGetQueryResultSeedsTheResultSetFromASelect(): void
20+
{
21+
$result = $this->getAdapter()->executeQuery('SELECT id, name, value FROM test');
22+
23+
self::assertSame($result->getFieldCount(), $result->getQueryResult()->getFieldCount());
24+
}
25+
26+
public function testGetQueryResultIteratesTheSelectedRows(): void
27+
{
28+
$result = $this->getAdapter()->executeQuery('SELECT name FROM test ORDER BY id');
29+
30+
$names = [];
31+
foreach ($result->getQueryResult() as $row) {
32+
$names[] = $row['name'];
33+
}
34+
35+
self::assertSame(['foo', 'bar'], $names);
36+
}
37+
38+
public function testAStatementReturningNoFieldsIsNotAQueryResult(): void
39+
{
40+
$result = $this->getAdapter()->executeQuery('SET search_path TO public');
41+
42+
self::assertFalse($result->isQueryResult());
43+
}
44+
45+
public function testGetQueryResultRejectsAStatementThatReturnsNoFields(): void
46+
{
47+
$result = $this->getAdapter()->executeQuery('SET search_path TO public');
48+
49+
$this->expectException(Exception\RuntimeException::class);
50+
$this->expectExceptionMessage(
51+
'Cannot produce a query result set from a result that is not a query result;'
52+
. ' check isQueryResult() first'
53+
);
54+
55+
$result->getQueryResult();
56+
}
57+
}

‎test/unit/ResultTest.php‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpDbTest\Pgsql;
6+
7+
use PhpDb\Adapter\Exception;
8+
use PhpDb\Pgsql\Result;
9+
use PhpDb\ResultSet\ResultSet;
10+
use PhpDbTestAsset\Pgsql\ResultStub;
11+
use PHPUnit\Framework\Attributes\CoversMethod;
12+
use PHPUnit\Framework\TestCase;
13+
14+
#[CoversMethod(Result::class, 'getQueryResult')]
15+
class ResultTest extends TestCase
16+
{
17+
public function testGetQueryResultSeedsTheResultSetFromTheResult(): void
18+
{
19+
$resultSet = (new ResultStub(true, 3))->getQueryResult();
20+
21+
self::assertSame(3, $resultSet->getFieldCount());
22+
}
23+
24+
public function testGetQueryResultClonesTheGivenPrototype(): void
25+
{
26+
$prototype = new ResultSet();
27+
28+
$resultSet = (new ResultStub(true, 1))->getQueryResult($prototype);
29+
30+
self::assertNotSame($prototype, $resultSet);
31+
}
32+
33+
public function testGetQueryResultRejectsAResultThatIsNotAQueryResult(): void
34+
{
35+
$this->expectException(Exception\RuntimeException::class);
36+
$this->expectExceptionMessage(
37+
'Cannot produce a query result set from a result that is not a query result;'
38+
. ' check isQueryResult() first'
39+
);
40+
41+
(new ResultStub(false))->getQueryResult();
42+
}
43+
}

0 commit comments

Comments
 (0)