Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
27 changes: 19 additions & 8 deletions agent/php_explain_mysqli.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,13 @@ static nr_status_t nr_php_explain_mysqli_add_fields_to_plan(
* Purpose : Execute a mysqli_stmt.
*
* Params : 1. The statement to execute.
* 2. The binding params array,
* optionally null if nothing to bind
* or if already bound
*
* Returns : NR_SUCCESS or NR_FAILURE.
*/
static nr_status_t nr_php_explain_mysqli_execute(zval* stmt TSRMLS_DC);
static nr_status_t nr_php_explain_mysqli_execute(zval* stmt, zval* params TSRMLS_DC);

/*
* Purpose : Retrieve the result of a mysqli_stmt that executed an EXPLAIN
Expand All @@ -73,13 +76,15 @@ static nr_explain_plan_t* nr_php_explain_mysqli_fetch_plan(
* 2. The handle of the MySQLi statement, or 0 if there was no
* statement.
* 3. The SQL to explain.
* 4. Params to be bound in execute
*
* Returns : A newly allocated explain plan, or NULL on error.
*/
static nr_explain_plan_t* nr_php_explain_mysqli_issue(
zval* link,
nr_php_object_handle_t handle,
const char* sql TSRMLS_DC);
const char* sql,
zval* params TSRMLS_DC);

/*
* Purpose : Prepare an EXPLAIN query.
Expand Down Expand Up @@ -120,14 +125,15 @@ nr_explain_plan_t* nr_php_explain_mysqli_query(const nrtxn_t* txn,
}

query = nr_strndup(sql, sql_len);
plan = nr_php_explain_mysqli_issue(link, 0, query TSRMLS_CC);
plan = nr_php_explain_mysqli_issue(link, 0, query, NULL TSRMLS_CC);
nr_free(query);

return plan;
}

nr_explain_plan_t* nr_php_explain_mysqli_stmt(const nrtxn_t* txn,
nr_php_object_handle_t handle,
zval* params,
nrtime_t start,
nrtime_t stop TSRMLS_DC) {
nrtime_t duration;
Expand Down Expand Up @@ -155,7 +161,7 @@ nr_explain_plan_t* nr_php_explain_mysqli_stmt(const nrtxn_t* txn,
return NULL;
}

plan = nr_php_explain_mysqli_issue(link, handle, query TSRMLS_CC);
plan = nr_php_explain_mysqli_issue(link, handle, query, params TSRMLS_CC);
nr_free(query);

return plan;
Expand Down Expand Up @@ -201,11 +207,15 @@ static nr_status_t nr_php_explain_mysqli_add_fields_to_plan(
return NR_SUCCESS;
}

static nr_status_t nr_php_explain_mysqli_execute(zval* stmt TSRMLS_DC) {
static nr_status_t nr_php_explain_mysqli_execute(zval* stmt, zval* params TSRMLS_DC) {
zval* retval;
nr_status_t status;

retval = nr_php_call(stmt, "execute");
if (params) {
retval = nr_php_call(stmt, "execute", params);
} else {
retval = nr_php_call(stmt, "execute");
}
status = nr_php_is_zval_true(retval) ? NR_SUCCESS : NR_FAILURE;

nr_php_zval_free(&retval);
Expand Down Expand Up @@ -310,7 +320,8 @@ static nr_explain_plan_t* nr_php_explain_mysqli_fetch_plan(
static nr_explain_plan_t* nr_php_explain_mysqli_issue(
zval* link,
nr_php_object_handle_t handle,
const char* sql TSRMLS_DC) {
const char* sql,
zval* params TSRMLS_DC) {
int error_reporting;
zval* link_dup = NULL;
nr_explain_plan_t* plan = NULL;
Expand All @@ -335,7 +346,7 @@ static nr_explain_plan_t* nr_php_explain_mysqli_issue(
}
}

if (NR_FAILURE == nr_php_explain_mysqli_execute(stmt TSRMLS_CC)) {
if (NR_FAILURE == nr_php_explain_mysqli_execute(stmt, params TSRMLS_CC)) {
goto end;
}

Expand Down
2 changes: 2 additions & 0 deletions agent/php_explain_mysqli.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ extern nr_explain_plan_t* nr_php_explain_mysqli_query(const nrtxn_t* txn,
*
* Params : 1. The transaction.
* 2. The mysqli_stmt object instance.
* 3. Optional params to be bound during execute
* 3. The start time of the original query.
* 4. The stop time of the original query.
*
Expand All @@ -46,6 +47,7 @@ extern nr_explain_plan_t* nr_php_explain_mysqli_query(const nrtxn_t* txn,
extern nr_explain_plan_t* nr_php_explain_mysqli_stmt(
const nrtxn_t* txn,
nr_php_object_handle_t handle,
zval* params,
nrtime_t start,
nrtime_t stop TSRMLS_DC);

Expand Down
17 changes: 14 additions & 3 deletions agent/php_internal_instrument.c
Original file line number Diff line number Diff line change
Expand Up @@ -1285,20 +1285,30 @@ NR_INNER_WRAPPER(mysqli_stmt_bind_param) {

/*
* Handle
* resource mysqli_stmt_execute ( object $link )
* resource mysqli_stmt::execute()
* resource mysqli_stmt_execute ( object $link, ?array $params )
* resource mysqli_stmt::execute( ?array $params )
*/
NR_INNER_WRAPPER(mysqli_stmt_execute) {
zval* stmt_obj = NULL;
const char* sqlstr = NULL;
int sqlstrlen;
int zcaught = 0;
zval* params = NULL;
nr_segment_t* segment = NULL;

if (FAILURE
== zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
ZEND_NUM_ARGS() TSRMLS_CC, "o", &stmt_obj)) {
ZEND_NUM_ARGS() TSRMLS_CC, "o|a!",
&stmt_obj, &params)) {
stmt_obj = NR_PHP_INTERNAL_FN_THIS();
if (FAILURE
== zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET,
ZEND_NUM_ARGS() TSRMLS_CC, "|a!",
&params)) {
nrl_warning(NRL_INSTRUMENT,
"failed to parse mysqli_stmt_execute params");
return;
}
}
sqlstr = nr_php_prepared_statement_find(stmt_obj, "mysqli" TSRMLS_CC);
sqlstrlen = nr_strlen(sqlstr);
Expand All @@ -1316,6 +1326,7 @@ NR_INNER_WRAPPER(mysqli_stmt_execute) {
if ((0 == NRTXNGLOBAL(generating_explain_plan))
&& nr_php_mysqli_zval_is_stmt(stmt_obj TSRMLS_CC)) {
plan = nr_php_explain_mysqli_stmt(NRPRG(txn), Z_OBJ_HANDLE_P(stmt_obj),
params,
segment->start_time,
segment->stop_time TSRMLS_CC);
}
Expand Down
145 changes: 145 additions & 0 deletions tests/integration/mysqli/test_stmt_execute_parameters_oo.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
<?php
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/*DESCRIPTION
The agent should report Database metrics for mysqli_stmt_execute when
called via object and passing binding parameters into execute.
*/

/*SKIPIF
<?php require("skipif.inc");
if (version_compare(PHP_VERSION, "8.1", "<")) {
die("skip: PHP < 8.1 not supported\n");
}
*/


/*INI
error_reporting = E_ALL & ~E_DEPRECATED
newrelic.transaction_tracer.explain_enabled = true
newrelic.transaction_tracer.explain_threshold = 0
newrelic.transaction_tracer.record_sql = obfuscated
*/

/*EXPECT
STATISTICS
*/

/*EXPECT_METRICS
[
"?? agent run id",
"?? start time",
"?? stop time",
[
[{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/all"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/all"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/allOther"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/MySQL/all"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/MySQL/allOther"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/MySQL/select"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/statement/MySQL/tables/select"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/statement/MySQL/tables/select",
"scope":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"OtherTransaction/all"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"OtherTransactionTotalTime"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"OtherTransactionTotalTime/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"Supportability/Logging/Forwarding/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"Supportability/Logging/Metrics/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"Supportability/Logging/LocalDecorating/PHP/disabled"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"Supportability/Logging/Labels/PHP/disabled"}, [1, "??", "??", "??", "??", "??"]]
]
]
*/




/*EXPECT_SLOW_SQLS
[
[
[
"OtherTransaction/php__FILE__",
"<unknown>",
"?? SQL ID",
"SELECT TABLE_NAME FROM information_schema.tables WHERE table_name=?",
"Datastore/statement/MySQL/tables/select",
1,
"?? total time",
"?? min time",
"?? max time",
{
"explain_plan": [
[
"id",
"select_type",
"table",
"type",
"possible_keys",
"key",
"key_len",
"ref",
"rows",
"Extra"
],
[
[
1,
"SIMPLE",
"tables",
"ALL",
null,
"TABLE_NAME",
null,
null,
null,
"Using where; Skip_open_table; Scanned 1 database"
]
]
],
"backtrace": [
" in mysqli_stmt::execute called at __FILE__ (??)",
" in test_stmt_execute called at __FILE__ (??)"
]
}
]
]
]
*/

/*EXPECT_TRACED_ERRORS
null
*/

require_once(realpath (dirname ( __FILE__ )) . '/mysqli.inc');

function test_stmt_execute($mysqli, $data)
{
$query = "SELECT TABLE_NAME FROM information_schema.tables WHERE table_name=?";
$stmt = $mysqli->prepare($query);

if (FALSE === $stmt->execute($data) ||
FALSE === $stmt->bind_result($name)) {
echo $stmt->error . "\n";
$stmt->close();
return;
}

$stmt->fetch();
echo $name . "\n";

$stmt->close();
}

$mysqli = new mysqli($MYSQL_HOST, $MYSQL_USER, $MYSQL_PASSWD, $MYSQL_DB, $MYSQL_PORT, $MYSQL_SOCKET);
if (mysqli_connect_errno()) {
echo mysqli_connect_error() . "\n";
exit(1);
}

test_stmt_execute($mysqli, ['STATISTICS']);
$mysqli->close();
Loading