diff --git a/NEWS b/NEWS index 9bad757d78f..c780da4c0cb 100644 --- a/NEWS +++ b/NEWS @@ -197,6 +197,7 @@ PHP NEWS . Added pg_change_password to alter an user's password. (David Carlier) . Added pg_put_copy_data/pg_put_copy_end to send COPY commands and signal the end of the COPY. (David Carlier) + . Added pg_socket_poll to poll on the connection. (David Carlier) - Phar: . Fixed bug GH-12532 (PharData created from zip has incorrect timestamp). diff --git a/UPGRADING b/UPGRADING index 77237e760d2..46e0d3ff743 100644 --- a/UPGRADING +++ b/UPGRADING @@ -549,6 +549,8 @@ PHP 8.4 UPGRADE NOTES transparently the password encryption from the database settings. . Added pg_put_copy_data to send COPY commands and pg_put_copy_end to send end-of-data to the server. + . Added pg_socket_poll to check if there is any read and/or write events + with an optional timeout. - Sodium: . Added the sodium_crypto_aead_aegis128l_*() and sodium_crypto_aead_aegis256l_*() diff --git a/ext/pgsql/config.m4 b/ext/pgsql/config.m4 index d0d4e4a26b9..f9ba584f319 100644 --- a/ext/pgsql/config.m4 +++ b/ext/pgsql/config.m4 @@ -68,6 +68,7 @@ if test "$PHP_PGSQL" != "no"; then AC_CHECK_LIB(pq, PQsetErrorContextVisibility, AC_DEFINE(HAVE_PG_CONTEXT_VISIBILITY,1,[PostgreSQL 9.6 or later])) AC_CHECK_LIB(pq, PQresultMemorySize, AC_DEFINE(HAVE_PG_RESULT_MEMORY_SIZE,1,[PostgreSQL 12 or later])) AC_CHECK_LIB(pq, PQchangePassword, AC_DEFINE(HAVE_PG_CHANGE_PASSWORD,1,[PostgreSQL 17 or later])) + AC_CHECK_LIB(pq, PQsocketPoll, AC_DEFINE(HAVE_PG_SOCKET_POLL,1,[PostgreSQL 17 or later])) LIBS=$old_LIBS LDFLAGS=$old_LDFLAGS diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index bf5b2fd8338..ef0cbac40b8 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -40,6 +40,9 @@ #include "php_globals.h" #include "zend_exceptions.h" #include "zend_attributes.h" +#if !defined(HAVE_PG_SOCKET_POLL) +#include "php_network.h" +#endif #ifdef HAVE_PGSQL @@ -443,6 +446,41 @@ static PGresult *PQchangePassword(PGconn *conn, const char *user, const char *pa } #endif +#if !defined(HAVE_PG_SOCKET_POLL) +static int PQsocketPoll(int socket, int read, int write, time_t timeout) +{ + if (!read && !write) + return 0; + + php_pollfd fd; + int ts = -1; + + fd.fd = socket; + fd.events = POLLERR; + fd.revents = 0; + + if (read) { + fd.events |= POLLIN; + } + + if (write) { + fd.events |= POLLOUT; + } + + if (timeout != (time_t)ts) { + time_t cur = time(NULL); + + if (timeout > cur) { + ts = (timeout - cur) * 1000; + } else { + ts = 0; + } + } + + return php_poll2(&fd, 1, ts); +} +#endif + /* {{{ PHP_INI */ PHP_INI_BEGIN() STD_PHP_INI_BOOLEAN( "pgsql.allow_persistent", "1", PHP_INI_SYSTEM, OnUpdateBool, allow_persistent, zend_pgsql_globals, pgsql_globals) @@ -6165,3 +6203,29 @@ PHP_FUNCTION(pg_put_copy_end) RETURN_LONG((zend_long)PQputCopyEnd(link->conn, err)); } + +PHP_FUNCTION(pg_socket_poll) +{ + zval *z_socket; + php_stream *stream; + php_socket_t socket; + zend_long read, write; + zend_long ts = -1; + + ZEND_PARSE_PARAMETERS_START(3, 4) + Z_PARAM_RESOURCE(z_socket) + Z_PARAM_LONG(read) + Z_PARAM_LONG(write) + Z_PARAM_OPTIONAL + Z_PARAM_LONG(ts) + ZEND_PARSE_PARAMETERS_END(); + + php_stream_from_zval(stream, z_socket); + + if (php_stream_cast(stream, PHP_STREAM_AS_SOCKETD, (void **)&socket, 0)) { + zend_argument_type_error(1, "invalid resource socket"); + RETURN_THROWS(); + } + + RETURN_LONG((zend_long)PQsocketPoll(socket, (int)read, (int)write, (int)ts)); +} diff --git a/ext/pgsql/pgsql.stub.php b/ext/pgsql/pgsql.stub.php index 88937161e76..189232c1566 100644 --- a/ext/pgsql/pgsql.stub.php +++ b/ext/pgsql/pgsql.stub.php @@ -952,6 +952,11 @@ namespace { function pg_put_copy_data(PgSql\Connection $connection, string $cmd): int {} function pg_put_copy_end(PgSql\Connection $connection, ?string $error = null): int {} + + /** + * @param resource $socket + */ + function pg_socket_poll($socket, int $read, int $write, int $timeout = -1): int {} } namespace PgSql { diff --git a/ext/pgsql/pgsql_arginfo.h b/ext/pgsql/pgsql_arginfo.h index 5f401a5ec9a..23460edbbde 100644 --- a/ext/pgsql/pgsql_arginfo.h +++ b/ext/pgsql/pgsql_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: edc626b255224ed76333223c90cfab062415637f */ + * Stub hash: 4a2a5778003aa741952e16617e5bdb2ad06e6e16 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_pg_connect, 0, 1, PgSql\\Connection, MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, connection_string, IS_STRING, 0) @@ -481,6 +481,13 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pg_put_copy_end, 0, 1, IS_LONG, ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, error, IS_STRING, 1, "null") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_pg_socket_poll, 0, 3, IS_LONG, 0) + ZEND_ARG_INFO(0, socket) + ZEND_ARG_TYPE_INFO(0, read, IS_LONG, 0) + ZEND_ARG_TYPE_INFO(0, write, IS_LONG, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, timeout, IS_LONG, 0, "-1") +ZEND_END_ARG_INFO() + ZEND_FUNCTION(pg_connect); ZEND_FUNCTION(pg_pconnect); ZEND_FUNCTION(pg_connect_poll); @@ -581,6 +588,7 @@ ZEND_FUNCTION(pg_result_memory_size); ZEND_FUNCTION(pg_change_password); ZEND_FUNCTION(pg_put_copy_data); ZEND_FUNCTION(pg_put_copy_end); +ZEND_FUNCTION(pg_socket_poll); static const zend_function_entry ext_functions[] = { ZEND_FE(pg_connect, arginfo_pg_connect) @@ -706,6 +714,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(pg_change_password, arginfo_pg_change_password) ZEND_FE(pg_put_copy_data, arginfo_pg_put_copy_data) ZEND_FE(pg_put_copy_end, arginfo_pg_put_copy_end) + ZEND_FE(pg_socket_poll, arginfo_pg_socket_poll) ZEND_FE_END }; diff --git a/ext/pgsql/tests/pg_socket_poll.phpt b/ext/pgsql/tests/pg_socket_poll.phpt new file mode 100644 index 00000000000..08aee8fff99 --- /dev/null +++ b/ext/pgsql/tests/pg_socket_poll.phpt @@ -0,0 +1,49 @@ +--TEST-- +PostgreSQL poll on connection's socket +--EXTENSIONS-- +pgsql +--SKIPIF-- + +--FILE-- +getMessage() . PHP_EOL; +} + +if (($topoll = pg_socket_poll($socket, 1, 1, 1)) === -1) die("pg_socket_poll failed"); +stream_set_blocking($socket, false); + +while (1) { + switch ($status = pg_connect_poll($db)) { + case PGSQL_POLLING_READING: + nb_is_writable($socket); + break; + case PGSQL_POLLING_WRITING: + nb_is_writable($socket); + break; + case PGSQL_POLLING_OK: + break 2; + default: + die("poll failed"); + } +} +assert(pg_connection_status($db) === PGSQL_CONNECTION_OK); +echo "OK"; + +pg_close($db); + +?> +--EXPECT-- +pg_socket_poll(): Argument #1 ($socket) invalid resource socket +OK