@@ -15,8 +15,8 @@ final class AsyncMySQLi
15
15
{
16
16
private static ?AsyncMySQLiPool $ pool = null ;
17
17
private static bool $ isInitialized = false ;
18
- private const POOL_INTERVAL = 10 ; // microseconds
19
- private const POOL_MAX_INTERVAL = 100 ; // microseconds
18
+ private const POOL_INTERVAL = 10 ;
19
+ private const POOL_MAX_INTERVAL = 100 ;
20
20
21
21
public static function init (array $ dbConfig , int $ poolSize = 10 ): void
22
22
{
@@ -54,22 +54,22 @@ public static function run(callable $callback): PromiseInterface
54
54
})();
55
55
}
56
56
57
- public static function query (string $ sql , array $ params = [], string $ types = '' ): PromiseInterface
57
+ public static function query (string $ sql , array $ params = [], ? string $ types = null ): PromiseInterface
58
58
{
59
59
return self ::executeAsyncQuery ($ sql , $ params , $ types , 'fetchAll ' );
60
60
}
61
61
62
- public static function fetchOne (string $ sql , array $ params = [], string $ types = '' ): PromiseInterface
62
+ public static function fetchOne (string $ sql , array $ params = [], ? string $ types = null ): PromiseInterface
63
63
{
64
64
return self ::executeAsyncQuery ($ sql , $ params , $ types , 'fetchOne ' );
65
65
}
66
66
67
- public static function execute (string $ sql , array $ params = [], string $ types = '' ): PromiseInterface
67
+ public static function execute (string $ sql , array $ params = [], ? string $ types = null ): PromiseInterface
68
68
{
69
69
return self ::executeAsyncQuery ($ sql , $ params , $ types , 'execute ' );
70
70
}
71
71
72
- public static function fetchValue (string $ sql , array $ params = [], string $ types = '' ): PromiseInterface
72
+ public static function fetchValue (string $ sql , array $ params = [], ? string $ types = null ): PromiseInterface
73
73
{
74
74
return self ::executeAsyncQuery ($ sql , $ params , $ types , 'fetchValue ' );
75
75
}
@@ -158,7 +158,7 @@ private static function startCancellableRacingTransaction(callable $transactionC
158
158
$ mysqli ->autocommit (true );
159
159
self ::getPool ()->release ($ mysqli );
160
160
} catch (Throwable $ e ) {
161
- error_log ("Failed to cancel transaction {$ index }: " . $ e ->getMessage ());
161
+ error_log ("Failed to cancel transaction {$ index }: " . $ e ->getMessage ());
162
162
self ::getPool ()->release ($ mysqli );
163
163
}
164
164
}
@@ -197,7 +197,7 @@ private static function finalizeRacingTransactions(array $mysqliConnections, int
197
197
echo "Transaction $ winnerIndex: Winner committed! \n" ;
198
198
self ::getPool ()->release ($ mysqli );
199
199
} catch (Throwable $ e ) {
200
- error_log ("Failed to commit winner transaction {$ winnerIndex }: " . $ e ->getMessage ());
200
+ error_log ("Failed to commit winner transaction {$ winnerIndex }: " . $ e ->getMessage ());
201
201
$ mysqli ->rollback ();
202
202
$ mysqli ->autocommit (true );
203
203
self ::getPool ()->release ($ mysqli );
@@ -220,7 +220,7 @@ private static function rollbackAllTransactions(array $mysqliConnections): Promi
220
220
$ mysqli ->autocommit (true );
221
221
self ::getPool ()->release ($ mysqli );
222
222
} catch (Throwable $ e ) {
223
- error_log ("Failed to rollback transaction {$ index }: " . $ e ->getMessage ());
223
+ error_log ("Failed to rollback transaction {$ index }: " . $ e ->getMessage ());
224
224
self ::getPool ()->release ($ mysqli );
225
225
}
226
226
})();
@@ -231,40 +231,93 @@ private static function rollbackAllTransactions(array $mysqliConnections): Promi
231
231
})();
232
232
}
233
233
234
- private static function executeAsyncQuery (string $ sql , array $ params , string $ types , string $ resultType ): PromiseInterface
234
+ private static function detectParameterTypes (array $ params ): string
235
+ {
236
+ $ types = '' ;
237
+
238
+ foreach ($ params as $ param ) {
239
+ $ types .= match (true ) {
240
+ $ param === null => 's ' ,
241
+ is_bool ($ param ) => 'i ' ,
242
+ is_int ($ param ) => 'i ' ,
243
+ is_float ($ param ) => 'd ' ,
244
+ is_resource ($ param ) => 'b ' ,
245
+ is_string ($ param ) && str_contains ($ param , "\0" ) => 'b ' ,
246
+ is_string ($ param ) => 's ' ,
247
+ is_array ($ param ) => 's ' ,
248
+ is_object ($ param ) => 's ' ,
249
+ default => 's ' ,
250
+ };
251
+ }
252
+
253
+ return $ types ;
254
+ }
255
+
256
+ private static function preprocessParameters (array $ params ): array
257
+ {
258
+ $ processedParams = [];
259
+
260
+ foreach ($ params as $ param ) {
261
+ $ processedParams [] = match (true ) {
262
+ $ param === null => null ,
263
+ is_bool ($ param ) => $ param ? 1 : 0 ,
264
+ is_int ($ param ) || is_float ($ param ) => $ param ,
265
+ is_resource ($ param ) => $ param , // Keep resource as-is for blob
266
+ is_string ($ param ) => $ param ,
267
+ is_array ($ param ) => json_encode ($ param ),
268
+ is_object ($ param ) && method_exists ($ param , '__toString ' ) => (string ) $ param ,
269
+ is_object ($ param ) => json_encode ($ param ),
270
+ default => (string ) $ param ,
271
+ };
272
+ }
273
+
274
+ return $ processedParams ;
275
+ }
276
+
277
+ private static function executeAsyncQuery (string $ sql , array $ params , ?string $ types , string $ resultType ): PromiseInterface
235
278
{
236
279
return Async::async (function () use ($ sql , $ params , $ types , $ resultType ) {
237
280
$ mysqli = await (self ::getPool ()->get ());
238
281
239
282
try {
240
- if (! empty ($ params )) {
283
+ if (count ($ params ) > 0 ) {
241
284
$ stmt = $ mysqli ->prepare ($ sql );
242
- if (! $ stmt ) {
243
- throw new \RuntimeException ('Prepare failed: ' . $ mysqli ->error );
285
+ if (!$ stmt ) {
286
+ throw new \RuntimeException ('Prepare failed: ' . $ mysqli ->error );
244
287
}
245
288
246
- if (empty ($ types )) {
289
+ if ($ types === null ) {
290
+ $ types = self ::detectParameterTypes ($ params );
291
+ }
292
+
293
+ if ($ types === '' ) {
247
294
$ types = str_repeat ('s ' , count ($ params ));
248
295
}
249
296
250
- if (! $ stmt ->bind_param ($ types , ...$ params )) {
251
- throw new \RuntimeException ('Bind param failed: ' .$ stmt ->error );
297
+ $ processedParams = self ::preprocessParameters ($ params );
298
+
299
+ if (!$ stmt ->bind_param ($ types , ...$ processedParams )) {
300
+ throw new \RuntimeException ('Bind param failed: ' . $ stmt ->error );
252
301
}
253
302
254
- if (! $ stmt ->execute ()) {
255
- throw new \RuntimeException ('Execute failed: ' . $ stmt ->error );
303
+ if (!$ stmt ->execute ()) {
304
+ throw new \RuntimeException ('Execute failed: ' . $ stmt ->error );
256
305
}
257
306
258
- if (stripos (trim ($ sql ), 'SELECT ' ) === 0 || stripos (trim ($ sql ), 'SHOW ' ) === 0 || stripos (trim ($ sql ), 'DESCRIBE ' ) === 0 ) {
307
+ if (
308
+ stripos (trim ($ sql ), 'SELECT ' ) === 0 ||
309
+ stripos (trim ($ sql ), 'SHOW ' ) === 0 ||
310
+ stripos (trim ($ sql ), 'DESCRIBE ' ) === 0
311
+ ) {
259
312
$ result = $ stmt ->get_result ();
260
313
} else {
261
314
$ result = true ;
262
315
}
263
316
264
317
return self ::processResult ($ result , $ resultType , $ stmt , $ mysqli );
265
318
} else {
266
- if (! $ mysqli ->query ($ sql , MYSQLI_ASYNC )) {
267
- throw new \RuntimeException ('Query failed: ' . $ mysqli ->error );
319
+ if (!$ mysqli ->query ($ sql , MYSQLI_ASYNC )) {
320
+ throw new \RuntimeException ('Query failed: ' . $ mysqli ->error );
268
321
}
269
322
270
323
$ result = await (self ::waitForAsyncCompletion ($ mysqli ));
@@ -320,7 +373,7 @@ private static function processResult($result, string $resultType, ?mysqli_stmt
320
373
if ($ result === false ) {
321
374
$ error = $ stmt ?->error ?? $ mysqli ?->error ?? 'Unknown error ' ;
322
375
323
- throw new \RuntimeException ('Query execution failed: ' . $ error );
376
+ throw new \RuntimeException ('Query execution failed: ' . $ error );
324
377
}
325
378
326
379
return match ($ resultType ) {
0 commit comments