Skip to content

pgsqlSetNoticeCallback #4823

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 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 77 additions & 2 deletions ext/pdo_pgsql/pgsql_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,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");
}
}
}
/* }}} */

Expand All @@ -121,6 +137,16 @@ static int pdo_pgsql_fetch_error_func(pdo_dbh_t *dbh, pdo_stmt_t *stmt, zval *in
}
/* }}} */

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)
{
Expand Down Expand Up @@ -203,6 +229,7 @@ static int pgsql_handle_closer(pdo_dbh_t *dbh) /* {{{ */
{
pdo_pgsql_db_handle *H = (pdo_pgsql_db_handle *)dbh->driver_data;
if (H) {
pdo_pgsql_cleanup_notice_callback(H);
if (H->server) {
PQfinish(H->server);
H->server = NULL;
Expand Down Expand Up @@ -1128,6 +1155,53 @@ static PHP_METHOD(PDO, pgsqlGetPid)
}
/* }}} */

/* {{{ proto bool PDO::pgsqlSetNoticeCallback(mixed callback)
Sets a callback to receive DB notices (after client_min_messages has been set) */
static PHP_METHOD(PDO, 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 dbh_methods[] = {
PHP_ME(PDO, pgsqlLOBCreate, NULL, ZEND_ACC_PUBLIC)
Expand All @@ -1139,6 +1213,7 @@ static const zend_function_entry dbh_methods[] = {
PHP_ME(PDO, pgsqlCopyToFile, NULL, ZEND_ACC_PUBLIC)
PHP_ME(PDO, pgsqlGetNotify, NULL, ZEND_ACC_PUBLIC)
PHP_ME(PDO, pgsqlGetPid, NULL, ZEND_ACC_PUBLIC)
PHP_ME(PDO, pgsqlSetNoticeCallback, NULL, ZEND_ACC_PUBLIC)
PHP_FE_END
};

Expand Down Expand Up @@ -1245,7 +1320,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;
Expand Down
6 changes: 6 additions & 0 deletions ext/pdo_pgsql/php_pdo_pgsql_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -45,6 +50,7 @@ typedef struct {
zend_bool emulate_prepares;
zend_bool disable_native_prepares; /* deprecated since 5.6 */
zend_bool disable_prepares;
pdo_pgsql_fci * notice_callback;
} pdo_pgsql_db_handle;

typedef struct {
Expand Down
22 changes: 22 additions & 0 deletions ext/pdo_pgsql/tests/issue78621.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?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";
?>
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
31 changes: 31 additions & 0 deletions ext/pdo_pgsql/tests/issue78621_closure.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--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 = '')
{
$db->pgsqlSetNoticeCallback(function($message) use($prefix) { echo $prefix.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?
ReNOTICE: I tampered your data, did you know?
array(1) {
[0]=>
array(1) {
["a"]=>
string(2) "oh"
}
}
Done
35 changes: 35 additions & 0 deletions ext/pdo_pgsql/tests/issue78621_method.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
--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"; }
}
$logger = new Logger();
function attach($db, $prefix = '')
{
global $logger;
$db->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