mirror of
https://github.com/php/php-src.git
synced 2024-11-23 09:54:15 +08:00
ext/pdo_pgsql: adding 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. close GH-6764
This commit is contained in:
parent
182fee1447
commit
c265b9085a
2
NEWS
2
NEWS
@ -178,6 +178,8 @@ PHP NEWS
|
||||
. Fixed native float support with pdo_pgsql query results. (Yurunsoft)
|
||||
. Added class PdoPgsql. (danack, kocsismate)
|
||||
. Retrieve the memory usage of the query result resource. (KentarouTakeda)
|
||||
. Added PDO::pgsqlSetNoticeCallBack method to receive DB notices.
|
||||
(outtersg)
|
||||
|
||||
- PDO_SQLITE:
|
||||
. Added class PdoSqlite. (danack, kocsismate)
|
||||
|
@ -522,6 +522,10 @@ PHP 8.4 UPGRADE NOTES
|
||||
. Added pcntl_getqos_class to get the QoS level (aka performance and related
|
||||
energy consumption) of the current process and pcntl_setqos_class to set it.
|
||||
|
||||
- PDO_PGSQL:
|
||||
. Added PDO::pgsqlSetNoticeCallback to allow a callback to be triggered on
|
||||
every notice sent (e.g. RAISE NOTICE).
|
||||
|
||||
- PGSQL:
|
||||
. Added pg_change_password to alter a given user's password. It handles
|
||||
transparently the password encryption from the database settings.
|
||||
|
@ -102,9 +102,16 @@ 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) /* {{{ */
|
||||
{
|
||||
/* pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data; */
|
||||
pdo_dbh_t * dbh = (pdo_dbh_t *)context;
|
||||
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_str(&zarg);
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
@ -125,6 +132,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) {
|
||||
zend_fcc_dtor(H->notice_callback);
|
||||
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 +246,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 +1242,30 @@ PHP_METHOD(PDO_PGSql_Ext, pgsqlGetPid)
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
/* {{{ 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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
pdo_dbh_t *dbh = Z_PDO_DBH_P(ZEND_THIS);
|
||||
PDO_CONSTRUCT_CHECK;
|
||||
|
||||
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
|
||||
|
||||
pdo_pgsql_cleanup_notice_callback(H);
|
||||
|
||||
if (ZEND_FCC_INITIALIZED(fcc)) {
|
||||
H->notice_callback = emalloc(sizeof(zend_fcall_info_cache));
|
||||
zend_fcc_dup(H->notice_callback, &fcc);
|
||||
}
|
||||
}
|
||||
/* }}} */
|
||||
|
||||
static const zend_function_entry *pdo_pgsql_get_driver_methods(pdo_dbh_t *dbh, int kind)
|
||||
{
|
||||
switch (kind) {
|
||||
@ -1341,7 +1383,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;
|
||||
|
@ -33,4 +33,7 @@ class PDO_PGSql_Ext {
|
||||
|
||||
/** @tentative-return-type */
|
||||
public function pgsqlGetPid(): int {}
|
||||
|
||||
/** @tentative-return-type */
|
||||
public function pgsqlSetNoticeCallback(?callable $callback): void {}
|
||||
}
|
||||
|
8
ext/pdo_pgsql/pgsql_driver_arginfo.h
generated
8
ext/pdo_pgsql/pgsql_driver_arginfo.h
generated
@ -1,5 +1,5 @@
|
||||
/* This is a generated file, edit the .stub.php file instead.
|
||||
* Stub hash: 9bb79af98dbb7c171fd9533aeabece4937a06cd2 */
|
||||
* 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,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_VOID, 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
|
||||
};
|
||||
|
@ -46,6 +46,7 @@ typedef struct {
|
||||
bool disable_native_prepares; /* deprecated since 5.6 */
|
||||
bool disable_prepares;
|
||||
HashTable *lob_streams;
|
||||
zend_fcall_info_cache *notice_callback;
|
||||
} pdo_pgsql_db_handle;
|
||||
|
||||
typedef struct {
|
||||
|
23
ext/pdo_pgsql/tests/issue78621.inc
Normal file
23
ext/pdo_pgsql/tests/issue78621.inc
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
require_once dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
|
||||
require_once dirname(__FILE__) . '/config.inc';
|
||||
$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()");
|
||||
$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";
|
||||
$db->rollback();
|
||||
?>
|
30
ext/pdo_pgsql/tests/issue78621.phpt
Normal file
30
ext/pdo_pgsql/tests/issue78621.phpt
Normal file
@ -0,0 +1,30 @@
|
||||
--TEST--
|
||||
pgsqlSetNoticeCallback catches Postgres "raise notice".
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if (!extension_loaded('pdo') || !extension_loaded('pdo_pgsql')) die('skip not loaded');
|
||||
require_once dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
|
||||
require_once dirname(__FILE__) . '/config.inc';
|
||||
PDOTest::skip();
|
||||
?>
|
||||
--FILE--
|
||||
<?php
|
||||
function disp($message) { echo trim($message)."\n"; }
|
||||
function dispRe($message) { echo "Re".trim($message)."\n"; }
|
||||
function attach($db, $prefix = '')
|
||||
{
|
||||
$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) {
|
||||
["a"]=>
|
||||
string(2) "oh"
|
||||
}
|
||||
}
|
||||
Done
|
58
ext/pdo_pgsql/tests/issue78621_closure.phpt
Normal file
58
ext/pdo_pgsql/tests/issue78621_closure.phpt
Normal file
@ -0,0 +1,58 @@
|
||||
--TEST--
|
||||
pgsqlSetNoticeCallback catches Postgres "raise notice".
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if (!extension_loaded('pdo') || !extension_loaded('pdo_pgsql')) die('skip not loaded');
|
||||
require_once dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
|
||||
require_once dirname(__FILE__) . '/config.inc';
|
||||
PDOTest::skip();
|
||||
?>
|
||||
--FILE--
|
||||
<?php
|
||||
function disp($message) { echo trim($message)."\n"; }
|
||||
function attach($db, $prefix = '')
|
||||
{
|
||||
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) {
|
||||
[0]=>
|
||||
array(1) {
|
||||
["a"]=>
|
||||
string(2) "oh"
|
||||
}
|
||||
}
|
||||
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
|
65
ext/pdo_pgsql/tests/issue78621_method.phpt
Normal file
65
ext/pdo_pgsql/tests/issue78621_method.phpt
Normal file
@ -0,0 +1,65 @@
|
||||
--TEST--
|
||||
pgsqlSetNoticeCallback catches Postgres "raise notice".
|
||||
--SKIPIF--
|
||||
<?php
|
||||
if (!extension_loaded('pdo') || !extension_loaded('pdo_pgsql')) die('skip not loaded');
|
||||
require_once dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
|
||||
require_once dirname(__FILE__) . '/config.inc';
|
||||
PDOTest::skip();
|
||||
?>
|
||||
--FILE--
|
||||
<?php
|
||||
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;
|
||||
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]=>
|
||||
array(1) {
|
||||
["a"]=>
|
||||
string(2) "oh"
|
||||
}
|
||||
}
|
||||
Done
|
Loading…
Reference in New Issue
Block a user