Skip to content

Commit 22de699

Browse files
committed
fix: split postgresql fetch into fetch and fetchSingle
- fixed TypeMapper phpstan templates
1 parent af9cdd1 commit 22de699

38 files changed

Lines changed: 459 additions & 163 deletions

File tree

src/bridge/phpunit/postgresql/tests/Flow/Bridge/PHPUnit/PostgreSQL/Tests/Double/FakeClient.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public function fetchInto(RowMapper $mapper, Sql|string $sql, array $parameters
7171
throw new \RuntimeException('Not implemented');
7272
}
7373

74-
public function fetchOne(Sql|string $sql, array $parameters = []) : array
74+
public function fetchOne(Sql|string $sql, array $parameters = []) : ?array
7575
{
7676
throw new \RuntimeException('Not implemented');
7777
}
@@ -106,6 +106,16 @@ public function fetchScalarString(Sql|string $sql, array $parameters = []) : str
106106
throw new \RuntimeException('Not implemented');
107107
}
108108

109+
public function fetchSingle(Sql|string $sql, array $parameters = []) : array
110+
{
111+
throw new \RuntimeException('Not implemented');
112+
}
113+
114+
public function fetchSingleInto(RowMapper $mapper, Sql|string $sql, array $parameters = []) : mixed
115+
{
116+
throw new \RuntimeException('Not implemented');
117+
}
118+
109119
public function getTransactionNestingLevel() : int
110120
{
111121
return $this->transactionLevel;

src/bridge/symfony/postgresql-bundle/tests/Flow/Bridge/Symfony/PostgreSqlBundle/Tests/Double/CacheSpyClient.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public function fetchInto(RowMapper $mapper, Sql|string $sql, array $parameters
7676
throw new \RuntimeException('Not implemented');
7777
}
7878

79-
public function fetchOne(Sql|string $sql, array $parameters = []) : array
79+
public function fetchOne(Sql|string $sql, array $parameters = []) : ?array
8080
{
8181
throw new \RuntimeException('Not implemented');
8282
}
@@ -111,6 +111,16 @@ public function fetchScalarString(Sql|string $sql, array $parameters = []) : str
111111
throw new \RuntimeException('Not implemented');
112112
}
113113

114+
public function fetchSingle(Sql|string $sql, array $parameters = []) : array
115+
{
116+
throw new \RuntimeException('Not implemented');
117+
}
118+
119+
public function fetchSingleInto(RowMapper $mapper, Sql|string $sql, array $parameters = []) : mixed
120+
{
121+
throw new \RuntimeException('Not implemented');
122+
}
123+
114124
public function getTransactionNestingLevel() : int
115125
{
116126
return 0;

src/bridge/symfony/postgresql-cache/tests/Flow/Bridge/Symfony/PostgreSQLCache/Tests/Unit/Double/SpyClient.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ class SpyClient implements Client
2525
public array $fetchAllReturn = [];
2626

2727
/**
28-
* @var array<string, mixed>
28+
* @var null|array<string, mixed>
2929
*/
30-
public array $fetchOneReturn = [];
30+
public ?array $fetchOneReturn = null;
3131

3232
/**
3333
* @var null|array<string, mixed>
@@ -99,7 +99,7 @@ public function fetchInto(RowMapper $mapper, Sql|string $sql, array $parameters
9999
throw new \RuntimeException('Not implemented');
100100
}
101101

102-
public function fetchOne(Sql|string $sql, array $parameters = []) : array
102+
public function fetchOne(Sql|string $sql, array $parameters = []) : ?array
103103
{
104104
$this->executedQueries[] = ['sql' => $sql instanceof Sql ? $sql->toSql() : $sql, 'parameters' => $parameters];
105105

@@ -136,6 +136,16 @@ public function fetchScalarString(Sql|string $sql, array $parameters = []) : str
136136
throw new \RuntimeException('Not implemented');
137137
}
138138

139+
public function fetchSingle(Sql|string $sql, array $parameters = []) : array
140+
{
141+
throw new \RuntimeException('Not implemented');
142+
}
143+
144+
public function fetchSingleInto(RowMapper $mapper, Sql|string $sql, array $parameters = []) : mixed
145+
{
146+
throw new \RuntimeException('Not implemented');
147+
}
148+
139149
public function getTransactionNestingLevel() : int
140150
{
141151
return $this->transactionLevel;

src/bridge/symfony/postgresql-messenger/src/Flow/Bridge/Symfony/PostgreSQLMessenger/Connection.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ public function send(string $body, array $headers, int $delay = 0) : string
176176
? $now->modify(\sprintf('%+d seconds', (int) ($delay / 1000)))
177177
: $now;
178178

179-
$row = $this->client->fetchOne(
179+
$row = $this->client->fetchSingle(
180180
insert()
181181
->into(table($this->tableName, $this->schemaName))
182182
->columns('body', 'headers', 'queue_name', 'created_at', 'available_at')

src/bridge/symfony/postgresql-messenger/tests/Flow/Bridge/Symfony/PostgreSQLMessenger/Tests/Unit/ConnectionTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ public function test_reject_deletes_message_by_id() : void
176176
public function test_send_inserts_message_and_returns_id() : void
177177
{
178178
$client = new SpyClient();
179-
$client->fetchOneReturn = ['id' => 100];
179+
$client->fetchSingleReturn = ['id' => 100];
180180
$connection = new Connection($client);
181181

182182
$id = $connection->send('hello', ['type' => 'App\\Foo']);
@@ -192,7 +192,7 @@ public function test_send_inserts_message_and_returns_id() : void
192192
public function test_send_returns_string_id_from_int_return() : void
193193
{
194194
$client = new SpyClient();
195-
$client->fetchOneReturn = ['id' => 42];
195+
$client->fetchSingleReturn = ['id' => 42];
196196
$connection = new Connection($client);
197197

198198
self::assertSame('42', $connection->send('body', []));
@@ -201,7 +201,7 @@ public function test_send_returns_string_id_from_int_return() : void
201201
public function test_send_returns_string_id_from_string_return() : void
202202
{
203203
$client = new SpyClient();
204-
$client->fetchOneReturn = ['id' => '99999999999999999'];
204+
$client->fetchSingleReturn = ['id' => '99999999999999999'];
205205
$connection = new Connection($client);
206206

207207
self::assertSame('99999999999999999', $connection->send('body', []));
@@ -210,7 +210,7 @@ public function test_send_returns_string_id_from_string_return() : void
210210
public function test_send_throws_on_unexpected_row_shape() : void
211211
{
212212
$client = new SpyClient();
213-
$client->fetchOneReturn = ['id' => null];
213+
$client->fetchSingleReturn = ['id' => null];
214214
$connection = new Connection($client);
215215

216216
$this->expectException(TransportException::class);
@@ -221,7 +221,7 @@ public function test_send_throws_on_unexpected_row_shape() : void
221221
public function test_send_with_delay_offsets_available_at() : void
222222
{
223223
$client = new SpyClient();
224-
$client->fetchOneReturn = ['id' => 1];
224+
$client->fetchSingleReturn = ['id' => 1];
225225
$connection = new Connection($client);
226226

227227
$connection->send('body', [], 5000);

src/bridge/symfony/postgresql-messenger/tests/Flow/Bridge/Symfony/PostgreSQLMessenger/Tests/Unit/Double/SpyClient.php

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,18 @@ class SpyClient implements Client
2424
*/
2525
public array $fetchAllReturn = [];
2626

27-
/**
28-
* @var array<string, mixed>
29-
*/
30-
public array $fetchOneReturn = [];
31-
3227
/**
3328
* @var null|array<string, mixed>
3429
*/
3530
public ?array $fetchReturn = null;
3631

3732
public int $fetchScalarIntReturn = 0;
3833

34+
/**
35+
* @var array<string, mixed>
36+
*/
37+
public array $fetchSingleReturn = [];
38+
3939
public int $transactionCallCount = 0;
4040

4141
public int $transactionLevel = 0;
@@ -101,11 +101,9 @@ public function fetchInto(RowMapper $mapper, Sql|string $sql, array $parameters
101101
throw new \RuntimeException('Not implemented');
102102
}
103103

104-
public function fetchOne(Sql|string $sql, array $parameters = []) : array
104+
public function fetchOne(Sql|string $sql, array $parameters = []) : ?array
105105
{
106-
$this->executedQueries[] = ['sql' => $sql instanceof Sql ? $sql->toSql() : $sql, 'parameters' => $parameters];
107-
108-
return $this->fetchOneReturn;
106+
throw new \RuntimeException('Not implemented');
109107
}
110108

111109
public function fetchOneInto(RowMapper $mapper, Sql|string $sql, array $parameters = []) : mixed
@@ -140,6 +138,18 @@ public function fetchScalarString(Sql|string $sql, array $parameters = []) : str
140138
throw new \RuntimeException('Not implemented');
141139
}
142140

141+
public function fetchSingle(Sql|string $sql, array $parameters = []) : array
142+
{
143+
$this->executedQueries[] = ['sql' => $sql instanceof Sql ? $sql->toSql() : $sql, 'parameters' => $parameters];
144+
145+
return $this->fetchSingleReturn;
146+
}
147+
148+
public function fetchSingleInto(RowMapper $mapper, Sql|string $sql, array $parameters = []) : mixed
149+
{
150+
throw new \RuntimeException('Not implemented');
151+
}
152+
143153
public function getTransactionNestingLevel() : int
144154
{
145155
return $this->transactionLevel;

src/bridge/symfony/postgresql-messenger/tests/Flow/Bridge/Symfony/PostgreSQLMessenger/Tests/Unit/FlowPostgreSqlSenderTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ final class FlowPostgreSqlSenderTest extends TestCase
1717
public function test_send_adds_transport_message_id_stamp() : void
1818
{
1919
$client = new SpyClient();
20-
$client->fetchOneReturn = ['id' => 777];
20+
$client->fetchSingleReturn = ['id' => 777];
2121
$sender = new FlowPostgreSqlSender(new Connection($client), new FakeSerializer());
2222

2323
$result = $sender->send(new Envelope((object) []));
@@ -30,7 +30,7 @@ public function test_send_adds_transport_message_id_stamp() : void
3030
public function test_send_encodes_envelope_and_passes_to_connection() : void
3131
{
3232
$client = new SpyClient();
33-
$client->fetchOneReturn = ['id' => 1];
33+
$client->fetchSingleReturn = ['id' => 1];
3434
$serializer = new FakeSerializer('my-body', ['stamp' => 'x']);
3535
$sender = new FlowPostgreSqlSender(new Connection($client), $serializer);
3636

@@ -75,7 +75,7 @@ public function encode(Envelope $envelope) : array
7575
public function test_send_with_delay_stamp_delegates_delay_to_connection() : void
7676
{
7777
$client = new SpyClient();
78-
$client->fetchOneReturn = ['id' => 1];
78+
$client->fetchSingleReturn = ['id' => 1];
7979
$sender = new FlowPostgreSqlSender(new Connection($client), new FakeSerializer());
8080

8181
$envelope = (new Envelope((object) []))->with(new DelayStamp(5000));
@@ -87,7 +87,7 @@ public function test_send_with_delay_stamp_delegates_delay_to_connection() : voi
8787
public function test_send_without_delay_stamp_passes_zero_delay() : void
8888
{
8989
$client = new SpyClient();
90-
$client->fetchOneReturn = ['id' => 1];
90+
$client->fetchSingleReturn = ['id' => 1];
9191
$sender = new FlowPostgreSqlSender(new Connection($client), new FakeSerializer());
9292

9393
$sender->send(new Envelope((object) []));
@@ -99,7 +99,7 @@ public function test_send_without_delay_stamp_passes_zero_delay() : void
9999
public function test_send_wraps_connection_exceptions() : void
100100
{
101101
$client = new class() extends SpyClient {
102-
public function fetchOne(Sql|string $sql, array $parameters = []) : array
102+
public function fetchSingle(Sql|string $sql, array $parameters = []) : array
103103
{
104104
throw new \RuntimeException('boom');
105105
}

src/bridge/symfony/postgresql-messenger/tests/Flow/Bridge/Symfony/PostgreSQLMessenger/Tests/Unit/FlowPostgreSqlTransportTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public function test_reject_delegates_to_receiver() : void
105105
public function test_send_delegates_to_sender_and_returns_envelope_with_stamp() : void
106106
{
107107
$client = new SpyClient();
108-
$client->fetchOneReturn = ['id' => 99];
108+
$client->fetchSingleReturn = ['id' => 99];
109109
$transport = new FlowPostgreSqlTransport(new Connection($client), new FakeSerializer());
110110

111111
$result = $transport->send(new Envelope((object) []));

src/bridge/symfony/postgresql-session/tests/Flow/Bridge/Symfony/PostgreSQLSession/Tests/Unit/Double/SpyClient.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public function fetchInto(RowMapper $mapper, Sql|string $sql, array $parameters
9494
throw new \RuntimeException('Not implemented');
9595
}
9696

97-
public function fetchOne(Sql|string $sql, array $parameters = []) : array
97+
public function fetchOne(Sql|string $sql, array $parameters = []) : ?array
9898
{
9999
throw new \RuntimeException('Not implemented');
100100
}
@@ -129,6 +129,16 @@ public function fetchScalarString(Sql|string $sql, array $parameters = []) : str
129129
throw new \RuntimeException('Not implemented');
130130
}
131131

132+
public function fetchSingle(Sql|string $sql, array $parameters = []) : array
133+
{
134+
throw new \RuntimeException('Not implemented');
135+
}
136+
137+
public function fetchSingleInto(RowMapper $mapper, Sql|string $sql, array $parameters = []) : mixed
138+
{
139+
throw new \RuntimeException('Not implemented');
140+
}
141+
132142
public function getTransactionNestingLevel() : int
133143
{
134144
return $this->transactionLevel;

src/lib/postgresql/src/Flow/PostgreSql/Client/Client.php

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace Flow\PostgreSql\Client;
66

77
use Flow\PostgreSql\AST\Transformers\ExplainConfig;
8-
use Flow\PostgreSql\Client\Exception\{ConnectionException, QueryException, TransactionException};
8+
use Flow\PostgreSql\Client\Exception\{ConnectionException, NoResultException, QueryException, TooManyRowsException, TransactionException};
99
use Flow\PostgreSql\Client\Types\ValueConverters;
1010
use Flow\PostgreSql\Explain\Plan\Plan;
1111
use Flow\PostgreSql\QueryBuilder\Sql;
@@ -138,31 +138,33 @@ public function fetchInto(
138138
) : mixed;
139139

140140
/**
141-
* Fetch exactly one row. Throws if result has 0 or more than 1 row.
142-
* Use when you expect precisely one result (e.g., SELECT by primary key).
141+
* Fetch at most one row. Returns null when the result is empty, throws when it has more than one row.
142+
* Use when you expect zero or one result (e.g., optional lookup by unique column).
143143
*
144144
* @param Sql|string $sql SQL query or query builder with $1, $2, ... placeholders
145145
* @param array<int, mixed> $parameters Positional parameters
146146
*
147-
* @throws QueryException When row count is not exactly 1
147+
* @throws QueryException
148+
* @throws TooManyRowsException When the result contains more than one row
148149
*
149-
* @return array<string, mixed>
150+
* @return null|array<string, mixed>
150151
*/
151-
public function fetchOne(Sql|string $sql, array $parameters = []) : array;
152+
public function fetchOne(Sql|string $sql, array $parameters = []) : ?array;
152153

153154
/**
154-
* Fetch exactly one row and map using the provided mapper.
155-
* Throws if result has 0 or more than 1 row.
155+
* Fetch at most one row and map using the provided mapper.
156+
* Returns null when the result is empty, throws when it has more than one row.
156157
*
157158
* @template T
158159
*
159160
* @param RowMapper<T> $mapper Mapper to apply to the row
160161
* @param Sql|string $sql SQL query or query builder with $1, $2, ... placeholders
161162
* @param array<int, mixed> $parameters Positional parameters
162163
*
163-
* @throws QueryException When row count is not exactly 1
164+
* @throws QueryException
165+
* @throws TooManyRowsException When the result contains more than one row
164166
*
165-
* @return T
167+
* @return null|T
166168
*/
167169
public function fetchOneInto(
168170
RowMapper $mapper,
@@ -222,6 +224,43 @@ public function fetchScalarInt(Sql|string $sql, array $parameters = []) : int;
222224
*/
223225
public function fetchScalarString(Sql|string $sql, array $parameters = []) : string;
224226

227+
/**
228+
* Fetch exactly one row. Throws if result has 0 or more than 1 row.
229+
* Use when you expect precisely one result (e.g., SELECT by primary key, INSERT ... RETURNING).
230+
*
231+
* @param Sql|string $sql SQL query or query builder with $1, $2, ... placeholders
232+
* @param array<int, mixed> $parameters Positional parameters
233+
*
234+
* @throws QueryException
235+
* @throws NoResultException When the result is empty
236+
* @throws TooManyRowsException When the result contains more than one row
237+
*
238+
* @return array<string, mixed>
239+
*/
240+
public function fetchSingle(Sql|string $sql, array $parameters = []) : array;
241+
242+
/**
243+
* Fetch exactly one row and map using the provided mapper.
244+
* Throws if result has 0 or more than 1 row.
245+
*
246+
* @template T
247+
*
248+
* @param RowMapper<T> $mapper Mapper to apply to the row
249+
* @param Sql|string $sql SQL query or query builder with $1, $2, ... placeholders
250+
* @param array<int, mixed> $parameters Positional parameters
251+
*
252+
* @throws QueryException
253+
* @throws NoResultException When the result is empty
254+
* @throws TooManyRowsException When the result contains more than one row
255+
*
256+
* @return T
257+
*/
258+
public function fetchSingleInto(
259+
RowMapper $mapper,
260+
Sql|string $sql,
261+
array $parameters = [],
262+
) : mixed;
263+
225264
/**
226265
* Get the current transaction nesting level.
227266
* 0 = no active transaction, 1 = top-level, 2+ = nested.

0 commit comments

Comments
 (0)