Commit 65ae1ad8471 for php.net
commit 65ae1ad84719ac8343f4137f3138b80152d85be0
Author: lacatoire <la.catoire@gmail.com>
Date: Thu Aug 27 21:05:19 2026 +0100
ext/pgsql: stop advertising PGSQL_DML_ASYNC where it is refused
pg_update() and pg_delete() list PGSQL_DML_ASYNC among the valid flags in
the error they raise, although their masks reject it and their helpers
assert it is never set. Conversely pg_insert() and pg_update() accept the
whole PGSQL_CONV_OPTS set but only named PGSQL_CONV_FORCE_NULL, so
PGSQL_CONV_IGNORE_DEFAULT and PGSQL_CONV_IGNORE_NOT_NULL are added.
While there, widen php_pgsql_delete()'s assert to PGSQL_DML_NO_CONV,
which pg_delete() accepts and the helper itself consults.
Close GH-23480
diff --git a/NEWS b/NEWS
index 6ac1e8e329a..871c1c9adde 100644
--- a/NEWS
+++ b/NEWS
@@ -16,6 +16,10 @@ PHP NEWS
. Added Pdo\Pgsql::ATTR_CHUNK_SIZE to fetch a result set in chunks of the
given number of rows. (KentarouTakeda)
+- PGSQL:
+ . Fixed the pg_insert(), pg_update() and pg_delete() flag error messages,
+ which did not name the set of flags actually accepted. (lacatoire)
+
- Phar:
. Fixed bug GH-23418 (Use-after-free when looking up mounted directories).
(Weilin Du)
diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c
index 9f0ca2c1a5e..791cbfd2c8f 100644
--- a/ext/pgsql/pgsql.c
+++ b/ext/pgsql/pgsql.c
@@ -5740,8 +5740,9 @@ PHP_FUNCTION(pg_insert)
}
if (option & ~(PGSQL_CONV_OPTS|PGSQL_DML_NO_CONV|PGSQL_DML_EXEC|PGSQL_DML_ASYNC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE)) {
- zend_argument_value_error(4, "must be a valid bit mask of PGSQL_CONV_FORCE_NULL, PGSQL_DML_NO_CONV, "
- "PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, PGSQL_DML_ASYNC, and PGSQL_DML_STRING");
+ zend_argument_value_error(4, "must be a valid bit mask of PGSQL_CONV_IGNORE_DEFAULT, PGSQL_CONV_FORCE_NULL, "
+ "PGSQL_CONV_IGNORE_NOT_NULL, PGSQL_DML_NO_CONV, PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, PGSQL_DML_ASYNC, "
+ "and PGSQL_DML_STRING");
RETURN_THROWS();
}
@@ -5972,8 +5973,9 @@ PHP_FUNCTION(pg_update)
}
if (option & ~(PGSQL_CONV_OPTS|PGSQL_DML_NO_CONV|PGSQL_DML_EXEC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE)) {
- zend_argument_value_error(5, "must be a valid bit mask of PGSQL_CONV_FORCE_NULL, PGSQL_DML_NO_CONV, "
- "PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, PGSQL_DML_ASYNC, and PGSQL_DML_STRING");
+ zend_argument_value_error(5, "must be a valid bit mask of PGSQL_CONV_IGNORE_DEFAULT, PGSQL_CONV_FORCE_NULL, "
+ "PGSQL_CONV_IGNORE_NOT_NULL, PGSQL_DML_NO_CONV, PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, "
+ "and PGSQL_DML_STRING");
RETURN_THROWS();
}
@@ -6004,7 +6006,7 @@ PHP_PGSQL_API zend_result php_pgsql_delete(PGconn *pg_link, const zend_string *t
ZEND_ASSERT(pg_link != NULL);
ZEND_ASSERT(table != NULL);
ZEND_ASSERT(Z_TYPE_P(ids_array) == IS_ARRAY);
- ZEND_ASSERT(!(opt & ~(PGSQL_CONV_FORCE_NULL|PGSQL_DML_EXEC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE)));
+ ZEND_ASSERT(!(opt & ~(PGSQL_CONV_FORCE_NULL|PGSQL_DML_NO_CONV|PGSQL_DML_EXEC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE)));
if (zend_hash_num_elements(Z_ARRVAL_P(ids_array)) == 0) {
return FAILURE;
@@ -6074,7 +6076,7 @@ PHP_FUNCTION(pg_delete)
if (option & ~(PGSQL_CONV_FORCE_NULL|PGSQL_DML_NO_CONV|PGSQL_DML_EXEC|PGSQL_DML_STRING|PGSQL_DML_ESCAPE)) {
zend_argument_value_error(4, "must be a valid bit mask of PGSQL_CONV_FORCE_NULL, PGSQL_DML_NO_CONV, "
- "PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, PGSQL_DML_ASYNC, and PGSQL_DML_STRING");
+ "PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, and PGSQL_DML_STRING");
RETURN_THROWS();
}
diff --git a/ext/pgsql/tests/pg_dml_option_flags.phpt b/ext/pgsql/tests/pg_dml_option_flags.phpt
new file mode 100644
index 00000000000..3f4b73671aa
--- /dev/null
+++ b/ext/pgsql/tests/pg_dml_option_flags.phpt
@@ -0,0 +1,64 @@
+--TEST--
+pg_insert()/pg_update()/pg_delete(): the flags refused are the flags the message names
+--EXTENSIONS--
+pgsql
+--SKIPIF--
+<?php include("inc/skipif.inc"); ?>
+--FILE--
+<?php
+
+include('inc/config.inc');
+$table_name = 'table_pg_dml_option_flags';
+
+$conn = pg_connect($conn_str);
+pg_query($conn, "CREATE TABLE {$table_name} (id INT, id2 INT)");
+
+/* PGSQL_DML_ASYNC is not part of the accepted mask of these two */
+try {
+ pg_update($conn, $table_name, ['id2' => 2], ['id' => 1], PGSQL_DML_ASYNC | PGSQL_DML_STRING);
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+try {
+ pg_delete($conn, $table_name, ['id' => 1], PGSQL_DML_ASYNC | PGSQL_DML_STRING);
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+/* 1 << 13 is not one of the flags at all */
+try {
+ pg_insert($conn, $table_name, ['id' => 1, 'id2' => 1], 1 << 13);
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+/* but PGSQL_DML_ASYNC is accepted by pg_insert() and pg_select() */
+var_dump(is_string(pg_insert($conn, $table_name, ['id' => 1, 'id2' => 1], PGSQL_DML_ASYNC | PGSQL_DML_STRING)));
+var_dump(is_string(pg_select($conn, $table_name, ['id' => 1], PGSQL_DML_ASYNC | PGSQL_DML_STRING)));
+
+/* every PGSQL_CONV_* flag the messages name is genuinely accepted */
+var_dump(is_string(pg_insert($conn, $table_name, ['id' => 1, 'id2' => 1], PGSQL_CONV_IGNORE_DEFAULT | PGSQL_DML_STRING)));
+var_dump(is_string(pg_update($conn, $table_name, ['id2' => 2], ['id' => 1], PGSQL_CONV_IGNORE_NOT_NULL | PGSQL_DML_STRING)));
+
+/* PGSQL_DML_NO_CONV is accepted by pg_delete() and reaches its helper */
+var_dump(is_string(pg_delete($conn, $table_name, ['id' => 1], PGSQL_DML_NO_CONV | PGSQL_DML_STRING)));
+
+?>
+--CLEAN--
+<?php
+include('inc/config.inc');
+$table_name = 'table_pg_dml_option_flags';
+
+$conn = pg_connect($conn_str);
+pg_query($conn, "DROP TABLE IF EXISTS {$table_name}");
+?>
+--EXPECT--
+pg_update(): Argument #5 ($flags) must be a valid bit mask of PGSQL_CONV_IGNORE_DEFAULT, PGSQL_CONV_FORCE_NULL, PGSQL_CONV_IGNORE_NOT_NULL, PGSQL_DML_NO_CONV, PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, and PGSQL_DML_STRING
+pg_delete(): Argument #4 ($flags) must be a valid bit mask of PGSQL_CONV_FORCE_NULL, PGSQL_DML_NO_CONV, PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, and PGSQL_DML_STRING
+pg_insert(): Argument #4 ($flags) must be a valid bit mask of PGSQL_CONV_IGNORE_DEFAULT, PGSQL_CONV_FORCE_NULL, PGSQL_CONV_IGNORE_NOT_NULL, PGSQL_DML_NO_CONV, PGSQL_DML_ESCAPE, PGSQL_DML_EXEC, PGSQL_DML_ASYNC, and PGSQL_DML_STRING
+bool(true)
+bool(true)
+bool(true)
+bool(true)
+bool(true)