17
17
use Revolt \EventLoop ;
18
18
use function Amp \async ;
19
19
20
- /** @internal */
20
+ /**
21
+ * @internal
22
+ *
23
+ * @psalm-type PgSqlTypeMap = array<int, PgSqlType> Map of OID to corresponding PgSqlType.
24
+ */
21
25
final class PgSqlHandle extends AbstractHandle
22
26
{
27
+ private const TYPE_QUERY = <<<SQL
28
+ SELECT t.oid, t.typcategory, t.typname, t.typdelim, t.typelem
29
+ FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON t.typnamespace=n.oid
30
+ WHERE t.typisdefined AND n.nspname IN ('pg_catalog', 'public') ORDER BY t.oid
31
+ SQL ;
32
+
23
33
private const DIAGNOSTIC_CODES = [
24
34
\PGSQL_DIAG_SEVERITY => "severity " ,
25
35
\PGSQL_DIAG_SQLSTATE => "sqlstate " ,
@@ -35,16 +45,16 @@ final class PgSqlHandle extends AbstractHandle
35
45
\PGSQL_DIAG_SOURCE_FUNCTION => "source_function " ,
36
46
];
37
47
38
- /** @var array<string, array<int, PgSqlType >> */
48
+ /** @var array<string, Future<PgSqlTypeMap >> */
39
49
private static array $ typeCache ;
40
50
41
51
private static ?\Closure $ errorHandler = null ;
42
52
43
53
/** @var \PgSql\Connection PostgreSQL connection handle. */
44
54
private ?\PgSql \Connection $ handle ;
45
55
46
- /** @var array<int, PgSqlType> */
47
- private readonly array $ types ;
56
+ /** @var PgSqlTypeMap|null */
57
+ private ? array $ types = null ;
48
58
49
59
/** @var array<non-empty-string, StatementStorage<string>> */
50
60
private array $ statements = [];
@@ -57,13 +67,11 @@ final class PgSqlHandle extends AbstractHandle
57
67
public function __construct (
58
68
\PgSql \Connection $ handle ,
59
69
$ socket ,
60
- string $ id ,
70
+ private readonly string $ id ,
61
71
PostgresConfig $ config ,
62
72
) {
63
73
$ this ->handle = $ handle ;
64
74
65
- $ this ->types = (self ::$ typeCache [$ id ] ??= self ::fetchTypes ($ handle ));
66
-
67
75
$ handle = &$ this ->handle ;
68
76
$ lastUsedAt = &$ this ->lastUsedAt ;
69
77
$ deferred = &$ this ->pendingOperation ;
@@ -171,35 +179,66 @@ public function __construct(
171
179
}
172
180
173
181
/**
174
- * @return array<int, PgSqlType >
182
+ * @return Future<PgSqlTypeMap >
175
183
*/
176
- private static function fetchTypes (\ PgSql \ Connection $ handle ): array
184
+ private function fetchTypes (): Future
177
185
{
178
- $ result = \pg_query ( $ handle , " SELECT t.oid, t.typcategory, t.typname, t.typdelim, t.typelem
179
- FROM pg_catalog.pg_type t JOIN pg_catalog.pg_namespace n ON t.typnamespace=n.oid
180
- WHERE t.typisdefined AND n.nspname IN ('pg_catalog', 'public') ORDER BY t.oid " );
186
+ if ( isset ( self :: $ typeCache [ $ this -> id ])) {
187
+ return self :: $ typeCache [ $ this -> id ];
188
+ }
181
189
182
- if ($ result === false ) {
183
- throw new SqlException (\pg_last_error ($ handle ));
190
+ \assert ($ this ->pendingOperation === null , 'Operation pending when fetching types! ' );
191
+
192
+ if ($ this ->handle === null ) {
193
+ throw new \Error ("The connection to the database has been closed " );
184
194
}
185
195
186
- $ types = [];
187
- while ($ row = \pg_fetch_array ($ result , mode: \PGSQL_NUM )) {
188
- [$ oid , $ typeCategory , $ typeName , $ delimiter , $ element ] = $ row ;
196
+ $ result = \pg_send_query ($ this ->handle , self ::TYPE_QUERY );
197
+ if ($ result === false ) {
198
+ $ this ->close ();
199
+ throw new SqlException (\pg_last_error ($ this ->handle ));
200
+ }
189
201
190
- \assert (
191
- \is_numeric ($ oid ) && \is_numeric ($ element ),
192
- "OID and element type expected to be integers " ,
193
- );
194
- \assert (
195
- \is_string ($ typeCategory ) && \is_string ($ typeName ) && \is_string ($ delimiter ),
196
- "Unexpected types in type catalog query results " ,
197
- );
202
+ $ this ->pendingOperation = $ queryDeferred = new DeferredFuture ();
203
+ $ typesDeferred = new DeferredFuture ();
198
204
199
- $ types [(int ) $ oid ] = new PgSqlType ($ typeCategory , $ typeName , $ delimiter , (int ) $ element );
205
+ EventLoop::reference ($ this ->poll );
206
+ if ($ result === 0 ) {
207
+ EventLoop::enable ($ this ->await );
200
208
}
201
209
202
- return $ types ;
210
+ EventLoop::queue (function () use ($ queryDeferred , $ typesDeferred ): void {
211
+ try {
212
+ $ result = $ queryDeferred ->getFuture ()->await ();
213
+ if (\pg_result_status ($ result ) !== \PGSQL_TUPLES_OK ) {
214
+ throw new SqlException (\pg_result_error ($ result ));
215
+ }
216
+
217
+ $ types = [];
218
+ while ($ row = \pg_fetch_array ($ result , mode: \PGSQL_NUM )) {
219
+ [$ oid , $ category , $ name , $ delimiter , $ element ] = $ row ;
220
+
221
+ \assert (
222
+ \is_numeric ($ oid ) && \is_numeric ($ element ),
223
+ "OID and element type expected to be integers " ,
224
+ );
225
+ \assert ( // For Psalm
226
+ \is_string ($ category ) && \is_string ($ name ) && \is_string ($ delimiter ),
227
+ "Unexpected nulls in type catalog query results " ,
228
+ );
229
+
230
+ $ types [(int ) $ oid ] = new PgSqlType ($ category , $ name , $ delimiter , (int ) $ element );
231
+ }
232
+
233
+ $ typesDeferred ->complete ($ types );
234
+ } catch (\Throwable $ exception ) {
235
+ $ this ->close ();
236
+ $ typesDeferred ->error ($ exception );
237
+ unset(self ::$ typeCache [$ this ->id ]);
238
+ }
239
+ });
240
+
241
+ return self ::$ typeCache [$ this ->id ] = $ typesDeferred ->getFuture ();
203
242
}
204
243
205
244
private static function getErrorHandler (): \Closure
@@ -224,12 +263,12 @@ public function isClosed(): bool
224
263
* @param \Closure $function Function to execute.
225
264
* @param mixed ...$args Arguments to pass to function.
226
265
*
227
- * @return \PgSql\Result
228
- *
229
266
* @throws SqlException
230
267
*/
231
268
private function send (\Closure $ function , mixed ...$ args ): mixed
232
269
{
270
+ $ this ->types ??= $ this ->fetchTypes ()->await ();
271
+
233
272
while ($ this ->pendingOperation ) {
234
273
try {
235
274
$ this ->pendingOperation ->getFuture ()->await ();
@@ -275,6 +314,8 @@ private function createResult(\PgSql\Result $result, string $sql): PostgresResul
275
314
throw new \Error ("The connection to the database has been closed " );
276
315
}
277
316
317
+ \assert ($ this ->types !== null , 'Expected type array to be populated before creating a result ' );
318
+
278
319
switch (\pg_result_status ($ result )) {
279
320
case \PGSQL_EMPTY_QUERY :
280
321
throw new SqlQueryError ("Empty query string " );
0 commit comments