Commit f3607f147fe for php.net

commit f3607f147fe64a317af30a2459909df63da1e2b2
Merge: 18cd1501df0 c1a7ab07eca
Author: David Carlier <devnexen@gmail.com>
Date:   Sun Sep 6 21:00:24 2026 +0100

    Merge branch 'PHP-8.4' into PHP-8.5

    * PHP-8.4:
      ext/pdo_pgsql: Fix CURSOR_SCROLL statements closing a nonexistent cursor

    # Conflicts:
    #       ext/pdo_pgsql/pgsql_statement.c
    #       ext/pdo_pgsql/php_pdo_pgsql_int.h

diff --cc ext/pdo_pgsql/pgsql_statement.c
index be3f31f62a3,8a33f5e91b7..b2af41a339b
--- a/ext/pdo_pgsql/pgsql_statement.c
+++ b/ext/pdo_pgsql/pgsql_statement.c
@@@ -56,23 -56,54 +56,64 @@@
  #define FLOAT8LABEL "float8"
  #define FLOAT8OID 701

 +#define FIN_DISCARD 0x1
 +#define FIN_CLOSE   0x2
 +#define FIN_ABORT   0x4
 +
+ #ifndef HAVE_PQCLOSEPORTAL
+ static bool pdo_pgsql_try_cmd(const char *cmd, const char *ok_sqlstate, pdo_pgsql_db_handle *H)
+ {
+ 	bool result = false;
+ 	char *q = NULL;
+ 	PGresult *res = NULL;
+
+ 	PGTransactionStatusType status = PQtransactionStatus(H->server);
+
+ 	switch (status) {
+ 		case PQTRANS_ACTIVE:
+ 		case PQTRANS_INERROR:
+ 			break;
+ 		case PQTRANS_INTRANS: /* failure must not abort the caller's transaction */
+ 			/* PQexec does not run the statements following a failed one */
+ 			spprintf(&q, 0, "SAVEPOINT pdo_pgsql_savepoint; %s; RELEASE SAVEPOINT pdo_pgsql_savepoint;", cmd);
+ 			res = PQexec(H->server, q);
+
+ 			if (PQresultStatus(res) != PGRES_COMMAND_OK) {
+ 				PQclear(PQexec(H->server, "ROLLBACK TO SAVEPOINT pdo_pgsql_savepoint; RELEASE SAVEPOINT pdo_pgsql_savepoint"));
+ 			}
+
+ 			break;
+ 		default:
+ 			res = PQexec(H->server, cmd);
+ 	}
+
+ 	if (PQresultStatus(res) == PGRES_COMMAND_OK) {
+ 		result = true;
+ 	} else if (res) {
+ 		const char *sqlstate = pdo_pgsql_sqlstate(res);
+
+ 		result = sqlstate && !strcmp(sqlstate, ok_sqlstate);
+ 	}
+
+ 	if (q) efree(q);
+ 	if (res) PQclear(res);
+
+ 	return result;
+ }
+ #endif

 -static int pgsql_stmt_dtor(pdo_stmt_t *stmt)
 +
 +static void pgsql_stmt_finish(pdo_pgsql_stmt *S, int fin_mode)
  {
 -	pdo_pgsql_stmt *S = (pdo_pgsql_stmt*)stmt->driver_data;
 -	bool server_obj_usable = !Z_ISUNDEF(stmt->database_object_handle)
 -		&& IS_OBJ_VALID(EG(objects_store).object_buckets[Z_OBJ_HANDLE(stmt->database_object_handle)])
 -		&& !(OBJ_FLAGS(Z_OBJ(stmt->database_object_handle)) & IS_OBJ_FREE_CALLED);
 +	pdo_pgsql_db_handle *H = S->H;
 +
 +	/* a buffered query may have already drained this statement's stream */
 +	if (S->is_running_unbuffered && H->running_stmt == S && S->result && (fin_mode & FIN_ABORT)) {
 +		PGcancel *cancel = PQgetCancel(H->server);
 +		char errbuf[256];
 +		PQcancel(cancel, errbuf, 256);
 +		PQfreeCancel(cancel);
 +	}

  	if (S->result) {
  		/* free the resource */
diff --cc ext/pdo_pgsql/php_pdo_pgsql_int.h
index 881b4e70465,c4ea431da21..60fa28bbcb6
--- a/ext/pdo_pgsql/php_pdo_pgsql_int.h
+++ b/ext/pdo_pgsql/php_pdo_pgsql_int.h
@@@ -69,9 -68,8 +69,10 @@@ struct pdo_pgsql_stmt
  	Oid *param_types;
  	int                     current_row;
  	bool is_prepared;
+ 	bool is_cursor_declared;
 -} pdo_pgsql_stmt;
 +	bool is_unbuffered;
 +	bool is_running_unbuffered;
 +};

  typedef struct {
  	Oid     oid;