From dc0c27fd903b2719507b2c542d4c3e79c6e75350 Mon Sep 17 00:00:00 2001 From: Guillaume Outters Date: Fri, 12 Mar 2021 00:16:18 +0100 Subject: [PATCH 01/10] pgsqlSetNoticeCallback Allows a callback to be triggered on every notice sent by PostgreSQL. Such notices can be sent with a RAISE NOTICE in PL/pgSQL; in a long running stored procedure, they prove useful as realtime checkpoint indicators. --- ext/pdo_pgsql/pgsql_driver.c | 78 +++++++++++++++++++++++++++- ext/pdo_pgsql/pgsql_driver.stub.php | 3 ++ ext/pdo_pgsql/pgsql_driver_arginfo.h | 8 ++- ext/pdo_pgsql/php_pdo_pgsql_int.h | 6 +++ 4 files changed, 92 insertions(+), 3 deletions(-) diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index 525f9cd1c15f7..3833abc2b4d57 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.c @@ -104,7 +104,23 @@ int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char * static void _pdo_pgsql_notice(pdo_dbh_t *dbh, const char *message) /* {{{ */ { -/* pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; */ + int ret; + zval zarg; + zval retval; + pdo_pgsql_fci * fc; + if ((fc = ((pdo_pgsql_db_handle *)dbh->driver_data)->notice_callback)) { + ZVAL_STRINGL(&zarg, (char *) message, strlen(message)); + fc->fci.param_count = 1; + fc->fci.params = &zarg; + fc->fci.retval = &retval; + if ((ret = zend_call_function(&fc->fci, &fc->fcc)) != FAILURE) { + zval_ptr_dtor(&retval); + } + zval_ptr_dtor(&zarg); + if (ret == FAILURE) { + pdo_raise_impl_error(dbh, NULL, "HY000", "could not call user-supplied function"); + } + } } /* }}} */ @@ -125,6 +141,16 @@ static void pdo_pgsql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *i } /* }}} */ +static void pdo_pgsql_cleanup_notice_callback(pdo_pgsql_db_handle *H) /* {{{ */ +{ + if (H->notice_callback) { + zval_ptr_dtor(&H->notice_callback->fci.function_name); + efree(H->notice_callback); + H->notice_callback = NULL; + } +} +/* }}} */ + /* {{{ pdo_pgsql_create_lob_stream */ static ssize_t pgsql_lob_write(php_stream *stream, const char *buf, size_t count) { @@ -229,6 +255,7 @@ static void pgsql_handle_closer(pdo_dbh_t *dbh) /* {{{ */ pefree(H->lob_streams, dbh->is_persistent); H->lob_streams = NULL; } + pdo_pgsql_cleanup_notice_callback(H); if (H->server) { PQfinish(H->server); H->server = NULL; @@ -1224,6 +1251,53 @@ PHP_METHOD(PDO_PGSql_Ext, pgsqlGetPid) } /* }}} */ +/* {{{ proto bool PDO::pgsqlSetNoticeCallback(mixed callback) + Sets a callback to receive DB notices (after client_min_messages has been set) */ +PHP_METHOD(PDO_PGSql_Ext, pgsqlSetNoticeCallback) +{ + zval *callback; + zend_string *cbname; + pdo_dbh_t *dbh; + pdo_pgsql_db_handle *H; + pdo_pgsql_fci *fc; + + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "z", &callback)) { + RETURN_FALSE; + } + + dbh = Z_PDO_DBH_P(getThis()); + PDO_CONSTRUCT_CHECK; + + H = (pdo_pgsql_db_handle *)dbh->driver_data; + + if (Z_TYPE_P(callback) == IS_NULL) { + pdo_pgsql_cleanup_notice_callback(H); + RETURN_TRUE; + } else { + if (!(fc = H->notice_callback)) { + fc = (pdo_pgsql_fci*)ecalloc(1, sizeof(pdo_pgsql_fci)); + } else { + zval_ptr_dtor(&fc->fci.function_name); + memcpy(&fc->fcc, &empty_fcall_info_cache, sizeof(fc->fcc)); + } + + if (FAILURE == zend_fcall_info_init(callback, 0, &fc->fci, &fc->fcc, &cbname, NULL)) { + php_error_docref(NULL, E_WARNING, "function '%s' is not callable", ZSTR_VAL(cbname)); + zend_string_release_ex(cbname, 0); + efree(fc); + H->notice_callback = NULL; + RETURN_FALSE; + } + Z_TRY_ADDREF_P(&fc->fci.function_name); + zend_string_release_ex(cbname, 0); + + H->notice_callback = fc; + + RETURN_TRUE; + } +} +/* }}} */ + static const zend_function_entry *pdo_pgsql_get_driver_methods(pdo_dbh_t *dbh, int kind) { switch (kind) { @@ -1341,7 +1415,7 @@ static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ goto cleanup; } - PQsetNoticeProcessor(H->server, (void(*)(void*,const char*))_pdo_pgsql_notice, (void *)&dbh); + PQsetNoticeProcessor(H->server, (void(*)(void*,const char*))_pdo_pgsql_notice, (void *)dbh); H->attached = 1; H->pgoid = -1; diff --git a/ext/pdo_pgsql/pgsql_driver.stub.php b/ext/pdo_pgsql/pgsql_driver.stub.php index cf3504fbbfb83..8c989be88927b 100644 --- a/ext/pdo_pgsql/pgsql_driver.stub.php +++ b/ext/pdo_pgsql/pgsql_driver.stub.php @@ -33,4 +33,7 @@ public function pgsqlGetNotify(int $fetchMode = PDO::FETCH_DEFAULT, int $timeout /** @tentative-return-type */ public function pgsqlGetPid(): int {} + + /** @tentative-return-type */ + public function pgsqlSetNoticeCallback(?callable $callback): bool {} } diff --git a/ext/pdo_pgsql/pgsql_driver_arginfo.h b/ext/pdo_pgsql/pgsql_driver_arginfo.h index 2b858e0a1ad1c..7f5f276f52b1b 100644 --- a/ext/pdo_pgsql/pgsql_driver_arginfo.h +++ b/ext/pdo_pgsql/pgsql_driver_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 9bb79af98dbb7c171fd9533aeabece4937a06cd2 */ + * Stub hash: f1bbbb97912bd83638aaf6a1d843ead2181894a0 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, 0, 2, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) @@ -46,6 +46,10 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlSetNoticeCallback, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 1) +ZEND_END_ARG_INFO() + ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromArray); ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyFromFile); ZEND_METHOD(PDO_PGSql_Ext, pgsqlCopyToArray); @@ -55,6 +59,7 @@ ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBOpen); ZEND_METHOD(PDO_PGSql_Ext, pgsqlLOBUnlink); ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetNotify); ZEND_METHOD(PDO_PGSql_Ext, pgsqlGetPid); +ZEND_METHOD(PDO_PGSql_Ext, pgsqlSetNoticeCallback); static const zend_function_entry class_PDO_PGSql_Ext_methods[] = { ZEND_ME(PDO_PGSql_Ext, pgsqlCopyFromArray, arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, ZEND_ACC_PUBLIC) @@ -66,5 +71,6 @@ static const zend_function_entry class_PDO_PGSql_Ext_methods[] = { ZEND_ME(PDO_PGSql_Ext, pgsqlLOBUnlink, arginfo_class_PDO_PGSql_Ext_pgsqlLOBUnlink, ZEND_ACC_PUBLIC) ZEND_ME(PDO_PGSql_Ext, pgsqlGetNotify, arginfo_class_PDO_PGSql_Ext_pgsqlGetNotify, ZEND_ACC_PUBLIC) ZEND_ME(PDO_PGSql_Ext, pgsqlGetPid, arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, ZEND_ACC_PUBLIC) + ZEND_ME(PDO_PGSql_Ext, pgsqlSetNoticeCallback, arginfo_class_PDO_PGSql_Ext_pgsqlSetNoticeCallback, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/pdo_pgsql/php_pdo_pgsql_int.h b/ext/pdo_pgsql/php_pdo_pgsql_int.h index ad48de384a3da..c4539c0602c50 100644 --- a/ext/pdo_pgsql/php_pdo_pgsql_int.h +++ b/ext/pdo_pgsql/php_pdo_pgsql_int.h @@ -32,6 +32,11 @@ typedef struct { char *errmsg; } pdo_pgsql_error_info; +typedef struct { + zend_fcall_info fci; + zend_fcall_info_cache fcc; +} pdo_pgsql_fci; + /* stuff we use in a pgsql database handle */ typedef struct { PGconn *server; @@ -46,6 +51,7 @@ typedef struct { bool disable_native_prepares; /* deprecated since 5.6 */ bool disable_prepares; HashTable *lob_streams; + pdo_pgsql_fci * notice_callback; } pdo_pgsql_db_handle; typedef struct { From 9cf61dcd7fa1e44622c4886870f08fc1abdb025e Mon Sep 17 00:00:00 2001 From: Guillaume Outters Date: Fri, 3 Jan 2020 14:27:50 +0100 Subject: [PATCH 02/10] pgsqlSetNoticeCallback: test case Add a test for a standard pgsqlSetNoticeCallback use. --- ext/pdo_pgsql/tests/issue78621.inc | 15 +++++++++++++++ ext/pdo_pgsql/tests/issue78621.phpt | 28 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 ext/pdo_pgsql/tests/issue78621.inc create mode 100644 ext/pdo_pgsql/tests/issue78621.phpt diff --git a/ext/pdo_pgsql/tests/issue78621.inc b/ext/pdo_pgsql/tests/issue78621.inc new file mode 100644 index 0000000000000..b1cdc990fd1ec --- /dev/null +++ b/ext/pdo_pgsql/tests/issue78621.inc @@ -0,0 +1,15 @@ +beginTransaction(); +$db->exec("create temporary table t (a varchar(3))"); +$db->exec("create function hey() returns trigger as \$\$ begin new.a := 'oh'; raise notice 'I tampered your data, did you know?'; return new; end; \$\$ language plpgsql"); +$db->exec("create trigger hop before insert on t for each row execute procedure hey()"); +$db->exec("insert into t values ('ah')"); +var_dump($db->query("select * from t")->fetchAll(PDO::FETCH_ASSOC)); +echo "Done\n"; +?> diff --git a/ext/pdo_pgsql/tests/issue78621.phpt b/ext/pdo_pgsql/tests/issue78621.phpt new file mode 100644 index 0000000000000..89213dd38472a --- /dev/null +++ b/ext/pdo_pgsql/tests/issue78621.phpt @@ -0,0 +1,28 @@ +--TEST-- +pgsqlSetNoticeCallback catches Postgres "raise notice". +--SKIPIF-- + +--FILE-- +pgsqlSetNoticeCallback('disp'); +} +require dirname(__FILE__) . '/issue78621.inc'; +?> +--EXPECT-- +NOTICE: I tampered your data, did you know? +array(1) { + [0]=> + array(1) { + ["a"]=> + string(2) "oh" + } +} +Done From dae95853222e850f7b6c54e07263cc2f28f4b26f Mon Sep 17 00:00:00 2001 From: Guillaume Outters Date: Fri, 3 Jan 2020 14:44:13 +0100 Subject: [PATCH 03/10] pgsqlSetNoticeCallback: test case for closure callback https://github.com/php/php-src/pull/4823#discussion_r360669537 Closure cannot be used as a callback if not persisted. --- ext/pdo_pgsql/tests/issue78621_closure.phpt | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 ext/pdo_pgsql/tests/issue78621_closure.phpt diff --git a/ext/pdo_pgsql/tests/issue78621_closure.phpt b/ext/pdo_pgsql/tests/issue78621_closure.phpt new file mode 100644 index 0000000000000..c116cde305d95 --- /dev/null +++ b/ext/pdo_pgsql/tests/issue78621_closure.phpt @@ -0,0 +1,30 @@ +--TEST-- +pgsqlSetNoticeCallback catches Postgres "raise notice". +--SKIPIF-- + +--FILE-- +pgsqlSetNoticeCallback(function($message) { echo trim($message)."\n"; }); + // https://github.com/php/php-src/pull/4823#pullrequestreview-335623806 + $eraseCallbackMemoryHere = (object)[1]; +} +require dirname(__FILE__) . '/issue78621.inc'; +?> +--EXPECT-- +NOTICE: I tampered your data, did you know? +array(1) { + [0]=> + array(1) { + ["a"]=> + string(2) "oh" + } +} +Done From 0ba336ca83226026ba1021944606defc0d2bd908 Mon Sep 17 00:00:00 2001 From: Guillaume Outters Date: Fri, 3 Jan 2020 17:36:21 +0100 Subject: [PATCH 04/10] pgsqlSetNoticeCallback: tests: ensure where are "multiple calls"-proof --- ext/pdo_pgsql/tests/issue78621.inc | 6 ++++++ ext/pdo_pgsql/tests/issue78621.phpt | 6 ++++-- ext/pdo_pgsql/tests/issue78621_closure.phpt | 5 +++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/ext/pdo_pgsql/tests/issue78621.inc b/ext/pdo_pgsql/tests/issue78621.inc index b1cdc990fd1ec..42236d803163d 100644 --- a/ext/pdo_pgsql/tests/issue78621.inc +++ b/ext/pdo_pgsql/tests/issue78621.inc @@ -10,6 +10,12 @@ $db->exec("create temporary table t (a varchar(3))"); $db->exec("create function hey() returns trigger as \$\$ begin new.a := 'oh'; raise notice 'I tampered your data, did you know?'; return new; end; \$\$ language plpgsql"); $db->exec("create trigger hop before insert on t for each row execute procedure hey()"); $db->exec("insert into t values ('ah')"); +attach($db, 'Re'); +$db->exec("delete from t"); +$db->exec("insert into t values ('ah')"); +$db->pgsqlSetNoticeCallback(null); +$db->exec("delete from t"); +$db->exec("insert into t values ('ah')"); var_dump($db->query("select * from t")->fetchAll(PDO::FETCH_ASSOC)); echo "Done\n"; ?> diff --git a/ext/pdo_pgsql/tests/issue78621.phpt b/ext/pdo_pgsql/tests/issue78621.phpt index 89213dd38472a..ee474a116cbdb 100644 --- a/ext/pdo_pgsql/tests/issue78621.phpt +++ b/ext/pdo_pgsql/tests/issue78621.phpt @@ -10,14 +10,16 @@ PDOTest::skip(); --FILE-- pgsqlSetNoticeCallback('disp'); + $db->pgsqlSetNoticeCallback('disp'.$prefix); } require dirname(__FILE__) . '/issue78621.inc'; ?> --EXPECT-- NOTICE: I tampered your data, did you know? +ReNOTICE: I tampered your data, did you know? array(1) { [0]=> array(1) { diff --git a/ext/pdo_pgsql/tests/issue78621_closure.phpt b/ext/pdo_pgsql/tests/issue78621_closure.phpt index c116cde305d95..f041f85f8e066 100644 --- a/ext/pdo_pgsql/tests/issue78621_closure.phpt +++ b/ext/pdo_pgsql/tests/issue78621_closure.phpt @@ -10,9 +10,9 @@ PDOTest::skip(); --FILE-- pgsqlSetNoticeCallback(function($message) { echo trim($message)."\n"; }); + $db->pgsqlSetNoticeCallback(function($message) use($prefix) { echo $prefix.trim($message)."\n"; }); // https://github.com/php/php-src/pull/4823#pullrequestreview-335623806 $eraseCallbackMemoryHere = (object)[1]; } @@ -20,6 +20,7 @@ require dirname(__FILE__) . '/issue78621.inc'; ?> --EXPECT-- NOTICE: I tampered your data, did you know? +ReNOTICE: I tampered your data, did you know? array(1) { [0]=> array(1) { From ae3d817f530a1f4414fc9a89fc691a1475dd5e6d Mon Sep 17 00:00:00 2001 From: Guillaume Outters Date: Fri, 3 Jan 2020 21:09:25 +0100 Subject: [PATCH 05/10] pgsqlSetNoticeCallback: test case for method callback --- ext/pdo_pgsql/tests/issue78621_method.phpt | 35 ++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 ext/pdo_pgsql/tests/issue78621_method.phpt diff --git a/ext/pdo_pgsql/tests/issue78621_method.phpt b/ext/pdo_pgsql/tests/issue78621_method.phpt new file mode 100644 index 0000000000000..749788b667bfd --- /dev/null +++ b/ext/pdo_pgsql/tests/issue78621_method.phpt @@ -0,0 +1,35 @@ +--TEST-- +pgsqlSetNoticeCallback catches Postgres "raise notice". +--SKIPIF-- + +--FILE-- +pgsqlSetNoticeCallback(array($logger, 'disp'.$prefix)); +} +require dirname(__FILE__) . '/issue78621.inc'; +?> +--EXPECT-- +NOTICE: I tampered your data, did you know? +ReNOTICE: I tampered your data, did you know? +array(1) { + [0]=> + array(1) { + ["a"]=> + string(2) "oh" + } +} +Done From cac65e8509775c7c91f163faf59120a2391dde0f Mon Sep 17 00:00:00 2001 From: Guillaume Outters Date: Thu, 9 Jan 2020 07:20:36 +0100 Subject: [PATCH 06/10] pgsqlSetNoticeCallback: tests: set client_min_messages --- ext/pdo_pgsql/tests/issue78621.inc | 1 + 1 file changed, 1 insertion(+) diff --git a/ext/pdo_pgsql/tests/issue78621.inc b/ext/pdo_pgsql/tests/issue78621.inc index 42236d803163d..76acd201c1794 100644 --- a/ext/pdo_pgsql/tests/issue78621.inc +++ b/ext/pdo_pgsql/tests/issue78621.inc @@ -6,6 +6,7 @@ $db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt'); attach($db); $db->beginTransaction(); +$db->exec("set client_min_messages to notice"); $db->exec("create temporary table t (a varchar(3))"); $db->exec("create function hey() returns trigger as \$\$ begin new.a := 'oh'; raise notice 'I tampered your data, did you know?'; return new; end; \$\$ language plpgsql"); $db->exec("create trigger hop before insert on t for each row execute procedure hey()"); From c757d0217dc978e2341c5bf740b19a87f0d1ede9 Mon Sep 17 00:00:00 2001 From: Guillaume Outters Date: Thu, 2 May 2024 22:04:05 +0200 Subject: [PATCH 07/10] pgsqlSetNoticeCallback: clean call to libpq Make the dirty casting work inside our callback, not while interacting with libpq's functions --- ext/pdo_pgsql/pgsql_driver.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index 3833abc2b4d57..afb814cfc4489 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.c @@ -102,12 +102,13 @@ int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char * } /* }}} */ -static void _pdo_pgsql_notice(pdo_dbh_t *dbh, const char *message) /* {{{ */ +static void _pdo_pgsql_notice(void *context, const char *message) /* {{{ */ { int ret; zval zarg; zval retval; pdo_pgsql_fci * fc; + pdo_dbh_t * dbh = (pdo_dbh_t *)context; if ((fc = ((pdo_pgsql_db_handle *)dbh->driver_data)->notice_callback)) { ZVAL_STRINGL(&zarg, (char *) message, strlen(message)); fc->fci.param_count = 1; @@ -1415,7 +1416,7 @@ static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ goto cleanup; } - PQsetNoticeProcessor(H->server, (void(*)(void*,const char*))_pdo_pgsql_notice, (void *)dbh); + PQsetNoticeProcessor(H->server, _pdo_pgsql_notice, (void *)dbh); H->attached = 1; H->pgoid = -1; From 5b128166a4f6a7512933cbba69ba1f8851c6e57c Mon Sep 17 00:00:00 2001 From: Guillaume Outters Date: Tue, 21 May 2024 00:13:51 +0200 Subject: [PATCH 08/10] pgsqlSetNoticeCallback: more tests see https://github.com/php/php-src/pull/6764#pullrequestreview-2038400861 --- ext/pdo_pgsql/tests/issue78621.inc | 1 + ext/pdo_pgsql/tests/issue78621_closure.phpt | 33 +++++++++++++++++++-- ext/pdo_pgsql/tests/issue78621_method.phpt | 32 +++++++++++++++++++- 3 files changed, 62 insertions(+), 4 deletions(-) diff --git a/ext/pdo_pgsql/tests/issue78621.inc b/ext/pdo_pgsql/tests/issue78621.inc index 76acd201c1794..8a42a1b50cfd2 100644 --- a/ext/pdo_pgsql/tests/issue78621.inc +++ b/ext/pdo_pgsql/tests/issue78621.inc @@ -19,4 +19,5 @@ $db->exec("delete from t"); $db->exec("insert into t values ('ah')"); var_dump($db->query("select * from t")->fetchAll(PDO::FETCH_ASSOC)); echo "Done\n"; +$db->rollback(); ?> diff --git a/ext/pdo_pgsql/tests/issue78621_closure.phpt b/ext/pdo_pgsql/tests/issue78621_closure.phpt index f041f85f8e066..55165e3aebc9e 100644 --- a/ext/pdo_pgsql/tests/issue78621_closure.phpt +++ b/ext/pdo_pgsql/tests/issue78621_closure.phpt @@ -12,13 +12,29 @@ PDOTest::skip(); function disp($message) { echo trim($message)."\n"; } function attach($db, $prefix = '') { - $db->pgsqlSetNoticeCallback(function($message) use($prefix) { echo $prefix.trim($message)."\n"; }); - // https://github.com/php/php-src/pull/4823#pullrequestreview-335623806 - $eraseCallbackMemoryHere = (object)[1]; + global $flavor; + switch($flavor) + { + case 0: + $db->pgsqlSetNoticeCallback(function($message) use($prefix) { echo $prefix.trim($message)."\n"; }); + // https://github.com/php/php-src/pull/4823#pullrequestreview-335623806 + $eraseCallbackMemoryHere = (object)[1]; + break; + case 1: + $closure = function($message) use($prefix) { echo $prefix.'('.get_class($this).')'.trim($message)."\n"; }; + $db->pgsqlSetNoticeCallback($closure->bindTo(new \stdClass)); + break; + } } +echo "Testing with a simple inline closure:\n"; +$flavor = 0; +require dirname(__FILE__) . '/issue78621.inc'; +echo "Testing with a postbound closure object:\n"; +++$flavor; require dirname(__FILE__) . '/issue78621.inc'; ?> --EXPECT-- +Testing with a simple inline closure: NOTICE: I tampered your data, did you know? ReNOTICE: I tampered your data, did you know? array(1) { @@ -29,3 +45,14 @@ array(1) { } } Done +Testing with a postbound closure object: +(stdClass)NOTICE: I tampered your data, did you know? +Re(stdClass)NOTICE: I tampered your data, did you know? +array(1) { + [0]=> + array(1) { + ["a"]=> + string(2) "oh" + } +} +Done diff --git a/ext/pdo_pgsql/tests/issue78621_method.phpt b/ext/pdo_pgsql/tests/issue78621_method.phpt index 749788b667bfd..2b3622ae4ab22 100644 --- a/ext/pdo_pgsql/tests/issue78621_method.phpt +++ b/ext/pdo_pgsql/tests/issue78621_method.phpt @@ -13,17 +13,47 @@ class Logger { public function disp($message) { echo trim($message)."\n"; } public function dispRe($message) { echo "Re".trim($message)."\n"; } + public function __call(string $method, array $args) + { + $realMethod = strtr($method, [ 'whatever' => 'disp' ]); + echo "$method trampoline for $realMethod\n"; + return call_user_func_array([ $this, $realMethod ], $args); + } } $logger = new Logger(); function attach($db, $prefix = '') { global $logger; - $db->pgsqlSetNoticeCallback(array($logger, 'disp'.$prefix)); + global $flavor; + switch($flavor) + { + case 0: $db->pgsqlSetNoticeCallback([ $logger, 'disp'.$prefix ]); break; + case 1: $db->pgsqlSetNoticeCallback([ $logger, 'whatever'.$prefix ]); break; + } } +echo "Testing with method explicitely plugged:\n"; +$flavor = 0; +require dirname(__FILE__) . '/issue78621.inc'; +echo "Testing with a bit of magic:\n"; +++$flavor; require dirname(__FILE__) . '/issue78621.inc'; ?> --EXPECT-- +Testing with method explicitely plugged: +NOTICE: I tampered your data, did you know? +ReNOTICE: I tampered your data, did you know? +array(1) { + [0]=> + array(1) { + ["a"]=> + string(2) "oh" + } +} +Done +Testing with a bit of magic: +whatever trampoline for disp NOTICE: I tampered your data, did you know? +whateverRe trampoline for dispRe ReNOTICE: I tampered your data, did you know? array(1) { [0]=> From 3d4d448bfacfa268464235876c65f2e6cacfdaeb Mon Sep 17 00:00:00 2001 From: Guillaume Outters Date: Tue, 21 May 2024 06:51:46 +0200 Subject: [PATCH 09/10] pgsqlSetNoticeCallback: standard ZPP and FCC use standard macros & funcs https://github.com/php/php-src/pull/6764#pullrequestreview-2038400861 --- ext/pdo_pgsql/pgsql_driver.c | 61 ++++++++----------------------- ext/pdo_pgsql/php_pdo_pgsql_int.h | 7 +--- 2 files changed, 17 insertions(+), 51 deletions(-) diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index afb814cfc4489..f21e97d73a9d7 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.c @@ -104,23 +104,13 @@ int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char * static void _pdo_pgsql_notice(void *context, const char *message) /* {{{ */ { - int ret; zval zarg; - zval retval; - pdo_pgsql_fci * fc; + zend_fcall_info_cache *fc; pdo_dbh_t * dbh = (pdo_dbh_t *)context; if ((fc = ((pdo_pgsql_db_handle *)dbh->driver_data)->notice_callback)) { ZVAL_STRINGL(&zarg, (char *) message, strlen(message)); - fc->fci.param_count = 1; - fc->fci.params = &zarg; - fc->fci.retval = &retval; - if ((ret = zend_call_function(&fc->fci, &fc->fcc)) != FAILURE) { - zval_ptr_dtor(&retval); - } + zend_call_known_fcc(fc, NULL, 1, &zarg, NULL); zval_ptr_dtor(&zarg); - if (ret == FAILURE) { - pdo_raise_impl_error(dbh, NULL, "HY000", "could not call user-supplied function"); - } } } /* }}} */ @@ -145,7 +135,7 @@ static void pdo_pgsql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *i static void pdo_pgsql_cleanup_notice_callback(pdo_pgsql_db_handle *H) /* {{{ */ { if (H->notice_callback) { - zval_ptr_dtor(&H->notice_callback->fci.function_name); + zend_fcc_dtor(H->notice_callback); efree(H->notice_callback); H->notice_callback = NULL; } @@ -1256,46 +1246,27 @@ PHP_METHOD(PDO_PGSql_Ext, pgsqlGetPid) Sets a callback to receive DB notices (after client_min_messages has been set) */ PHP_METHOD(PDO_PGSql_Ext, pgsqlSetNoticeCallback) { - zval *callback; - zend_string *cbname; pdo_dbh_t *dbh; pdo_pgsql_db_handle *H; - pdo_pgsql_fci *fc; - - if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "z", &callback)) { - RETURN_FALSE; - } + zend_fcall_info fci = empty_fcall_info; + zend_fcall_info_cache fcc = empty_fcall_info_cache; dbh = Z_PDO_DBH_P(getThis()); PDO_CONSTRUCT_CHECK; H = (pdo_pgsql_db_handle *)dbh->driver_data; - - if (Z_TYPE_P(callback) == IS_NULL) { - pdo_pgsql_cleanup_notice_callback(H); - RETURN_TRUE; - } else { - if (!(fc = H->notice_callback)) { - fc = (pdo_pgsql_fci*)ecalloc(1, sizeof(pdo_pgsql_fci)); - } else { - zval_ptr_dtor(&fc->fci.function_name); - memcpy(&fc->fcc, &empty_fcall_info_cache, sizeof(fc->fcc)); - } - - if (FAILURE == zend_fcall_info_init(callback, 0, &fc->fci, &fc->fcc, &cbname, NULL)) { - php_error_docref(NULL, E_WARNING, "function '%s' is not callable", ZSTR_VAL(cbname)); - zend_string_release_ex(cbname, 0); - efree(fc); - H->notice_callback = NULL; - RETURN_FALSE; - } - Z_TRY_ADDREF_P(&fc->fci.function_name); - zend_string_release_ex(cbname, 0); - - H->notice_callback = fc; - - RETURN_TRUE; + + pdo_pgsql_cleanup_notice_callback(H); + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "F!", &fci, &fcc)) { + RETURN_FALSE; } + + if (ZEND_FCC_INITIALIZED(fcc)) { + H->notice_callback = ecalloc(1, sizeof(zend_fcall_info_cache)); + zend_fcc_dup(H->notice_callback, &fcc); + } + + RETURN_TRUE; } /* }}} */ diff --git a/ext/pdo_pgsql/php_pdo_pgsql_int.h b/ext/pdo_pgsql/php_pdo_pgsql_int.h index c4539c0602c50..daa3357d79acb 100644 --- a/ext/pdo_pgsql/php_pdo_pgsql_int.h +++ b/ext/pdo_pgsql/php_pdo_pgsql_int.h @@ -32,11 +32,6 @@ typedef struct { char *errmsg; } pdo_pgsql_error_info; -typedef struct { - zend_fcall_info fci; - zend_fcall_info_cache fcc; -} pdo_pgsql_fci; - /* stuff we use in a pgsql database handle */ typedef struct { PGconn *server; @@ -51,7 +46,7 @@ typedef struct { bool disable_native_prepares; /* deprecated since 5.6 */ bool disable_prepares; HashTable *lob_streams; - pdo_pgsql_fci * notice_callback; + zend_fcall_info_cache *notice_callback; } pdo_pgsql_db_handle; typedef struct { From b0df76817c6fbe455a5192d6ca0915f0c7db4717 Mon Sep 17 00:00:00 2001 From: Guillaume Outters Date: Tue, 21 May 2024 22:54:57 +0200 Subject: [PATCH 10/10] pgsqlSetNoticeCallback: cleanup --- ext/pdo_pgsql/pgsql_driver.c | 28 ++++++++++++---------------- ext/pdo_pgsql/pgsql_driver.stub.php | 2 +- ext/pdo_pgsql/pgsql_driver_arginfo.h | 4 ++-- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index f21e97d73a9d7..a540393c7365d 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.c @@ -104,13 +104,13 @@ int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char * static void _pdo_pgsql_notice(void *context, const char *message) /* {{{ */ { - zval zarg; - zend_fcall_info_cache *fc; pdo_dbh_t * dbh = (pdo_dbh_t *)context; - if ((fc = ((pdo_pgsql_db_handle *)dbh->driver_data)->notice_callback)) { - ZVAL_STRINGL(&zarg, (char *) message, strlen(message)); + zend_fcall_info_cache *fc = ((pdo_pgsql_db_handle *)dbh->driver_data)->notice_callback; + if (fc) { + zval zarg; + ZVAL_STRING(&zarg, message); zend_call_known_fcc(fc, NULL, 1, &zarg, NULL); - zval_ptr_dtor(&zarg); + zval_ptr_dtor_str(&zarg); } } /* }}} */ @@ -1242,31 +1242,27 @@ PHP_METHOD(PDO_PGSql_Ext, pgsqlGetPid) } /* }}} */ -/* {{{ proto bool PDO::pgsqlSetNoticeCallback(mixed callback) +/* {{{ proto void PDO::pgsqlSetNoticeCallback(mixed callback) Sets a callback to receive DB notices (after client_min_messages has been set) */ PHP_METHOD(PDO_PGSql_Ext, pgsqlSetNoticeCallback) { - pdo_dbh_t *dbh; - pdo_pgsql_db_handle *H; zend_fcall_info fci = empty_fcall_info; zend_fcall_info_cache fcc = empty_fcall_info_cache; + if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "F!", &fci, &fcc)) { + RETURN_THROWS(); + } - dbh = Z_PDO_DBH_P(getThis()); + pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS); PDO_CONSTRUCT_CHECK; - H = (pdo_pgsql_db_handle *)dbh->driver_data; + pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; pdo_pgsql_cleanup_notice_callback(H); - if (FAILURE == zend_parse_parameters(ZEND_NUM_ARGS(), "F!", &fci, &fcc)) { - RETURN_FALSE; - } if (ZEND_FCC_INITIALIZED(fcc)) { - H->notice_callback = ecalloc(1, sizeof(zend_fcall_info_cache)); + H->notice_callback = emalloc(sizeof(zend_fcall_info_cache)); zend_fcc_dup(H->notice_callback, &fcc); } - - RETURN_TRUE; } /* }}} */ diff --git a/ext/pdo_pgsql/pgsql_driver.stub.php b/ext/pdo_pgsql/pgsql_driver.stub.php index 8c989be88927b..5e09ec683b29a 100644 --- a/ext/pdo_pgsql/pgsql_driver.stub.php +++ b/ext/pdo_pgsql/pgsql_driver.stub.php @@ -35,5 +35,5 @@ public function pgsqlGetNotify(int $fetchMode = PDO::FETCH_DEFAULT, int $timeout public function pgsqlGetPid(): int {} /** @tentative-return-type */ - public function pgsqlSetNoticeCallback(?callable $callback): bool {} + public function pgsqlSetNoticeCallback(?callable $callback): void {} } diff --git a/ext/pdo_pgsql/pgsql_driver_arginfo.h b/ext/pdo_pgsql/pgsql_driver_arginfo.h index 7f5f276f52b1b..d38b0fb7577dc 100644 --- a/ext/pdo_pgsql/pgsql_driver_arginfo.h +++ b/ext/pdo_pgsql/pgsql_driver_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: f1bbbb97912bd83638aaf6a1d843ead2181894a0 */ + * Stub hash: 14174ab18f198b9916f83986d10c93b657d8ffb9 */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlCopyFromArray, 0, 2, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, tableName, IS_STRING, 0) @@ -46,7 +46,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlGetPid, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlSetNoticeCallback, 0, 1, _IS_BOOL, 0) +ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_PDO_PGSql_Ext_pgsqlSetNoticeCallback, 0, 1, IS_VOID, 0) ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 1) ZEND_END_ARG_INFO()