Skip to content

pgsqlSetNoticeCallback #6764

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 10 commits into from
Closed
52 changes: 49 additions & 3 deletions ext/pdo_pgsql/pgsql_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -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; */
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_call_known_fcc(fc, NULL, 1, &zarg, NULL);
zval_ptr_dtor(&zarg);
}
}
/* }}} */

Expand All @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1224,6 +1242,34 @@ 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)
{
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;

dbh = Z_PDO_DBH_P(getThis());
PDO_CONSTRUCT_CHECK;

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));
zend_fcc_dup(H->notice_callback, &fcc);
}

RETURN_TRUE;
}
/* }}} */

static const zend_function_entry *pdo_pgsql_get_driver_methods(pdo_dbh_t *dbh, int kind)
{
switch (kind) {
Expand Down Expand Up @@ -1341,7 +1387,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;
Expand Down
3 changes: 3 additions & 0 deletions ext/pdo_pgsql/pgsql_driver.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
}
8 changes: 7 additions & 1 deletion ext/pdo_pgsql/pgsql_driver_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ext/pdo_pgsql/php_pdo_pgsql_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
23 changes: 23 additions & 0 deletions ext/pdo_pgsql/tests/issue78621.inc
Original file line number Diff line number Diff line change
@@ -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 changes: 30 additions & 0 deletions ext/pdo_pgsql/tests/issue78621.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 58 additions & 0 deletions ext/pdo_pgsql/tests/issue78621_closure.phpt
Original file line number Diff line number Diff line change
@@ -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 changes: 65 additions & 0 deletions ext/pdo_pgsql/tests/issue78621_method.phpt
Original file line number Diff line number Diff line change
@@ -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