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,50 @@ 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 ( $ this -> handle === null ) {
187
+ throw new \ Error ( " The connection to the database has been closed " );
188
+ }
181
189
190
+ $ result = \pg_send_query ($ this ->handle , self ::TYPE_QUERY );
182
191
if ($ result === false ) {
183
- throw new SqlException (\pg_last_error ($ handle ));
192
+ $ this ->close ();
193
+ throw new SqlException (\pg_last_error ($ this ->handle ));
184
194
}
185
195
186
- $ types = [];
187
- while ($ row = \pg_fetch_array ($ result , mode: \PGSQL_NUM )) {
188
- [$ oid , $ typeCategory , $ typeName , $ delimiter , $ element ] = $ row ;
196
+ $ this ->pendingOperation = $ queryDeferred = new DeferredFuture ();
197
+ $ typesDeferred = new DeferredFuture ();
189
198
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
- );
198
-
199
- $ types [(int ) $ oid ] = new PgSqlType ($ typeCategory , $ typeName , $ delimiter , (int ) $ element );
199
+ EventLoop::reference ($ this ->poll );
200
+ if ($ result === 0 ) {
201
+ EventLoop::enable ($ this ->await );
200
202
}
201
203
202
- return $ types ;
204
+ EventLoop::queue (function () use ($ queryDeferred , $ typesDeferred ): void {
205
+ try {
206
+ $ result = $ queryDeferred ->getFuture ()->await ();
207
+ if (\pg_result_status ($ result ) !== \PGSQL_TUPLES_OK ) {
208
+ throw new SqlException (\pg_result_error ($ result ));
209
+ }
210
+
211
+ $ types = [];
212
+ while ($ row = \pg_fetch_array ($ result , mode: \PGSQL_NUM )) {
213
+ [$ oid , $ typeCategory , $ typeName , $ delimiter , $ element ] = $ row ;
214
+ $ types [(int ) $ oid ] = new PgSqlType ($ typeCategory , $ typeName , $ delimiter , (int ) $ element );
215
+ }
216
+
217
+ $ typesDeferred ->complete ($ types );
218
+ } catch (\Throwable $ exception ) {
219
+ $ this ->close ();
220
+ $ typesDeferred ->error ($ exception );
221
+ unset(self ::$ typeCache [$ this ->id ]);
222
+ }
223
+ });
224
+
225
+ return $ typesDeferred ->getFuture ();
203
226
}
204
227
205
228
private static function getErrorHandler (): \Closure
@@ -224,12 +247,12 @@ public function isClosed(): bool
224
247
* @param \Closure $function Function to execute.
225
248
* @param mixed ...$args Arguments to pass to function.
226
249
*
227
- * @return \PgSql\Result
228
- *
229
250
* @throws SqlException
230
251
*/
231
252
private function send (\Closure $ function , mixed ...$ args ): mixed
232
253
{
254
+ $ this ->types ??= (self ::$ typeCache [$ this ->id ] ??= $ this ->fetchTypes ())->await ();
255
+
233
256
while ($ this ->pendingOperation ) {
234
257
try {
235
258
$ this ->pendingOperation ->getFuture ()->await ();
@@ -275,6 +298,8 @@ private function createResult(\PgSql\Result $result, string $sql): PostgresResul
275
298
throw new \Error ("The connection to the database has been closed " );
276
299
}
277
300
301
+ \assert ($ this ->types !== null , 'Expected type array to be populated before creating a result ' );
302
+
278
303
switch (\pg_result_status ($ result )) {
279
304
case \PGSQL_EMPTY_QUERY :
280
305
throw new SqlQueryError ("Empty query string " );
0 commit comments