Skip to content

Commit dc162a4

Browse files
lavaroukovsheninzsistla
authored
fix(agent): don't skip arguments when calling mysqli::real_connect (#976)
Co-authored-by: Konstantin Kovshenin <[email protected]> Co-authored-by: Amber Sistla <[email protected]>
1 parent 597bad6 commit dc162a4

File tree

4 files changed

+342
-26
lines changed

4 files changed

+342
-26
lines changed

agent/php_mysqli.c

+35-26
Original file line numberDiff line numberDiff line change
@@ -398,42 +398,51 @@ static nr_status_t nr_php_mysqli_link_real_connect(
398398
zval* link,
399399
const nr_mysqli_metadata_link_t* metadata TSRMLS_DC) {
400400
zend_ulong argc = 0;
401+
zend_ulong arg_required = 0;
401402
zval* argv[7] = {0};
402403
zend_ulong i;
403404
zval* retval = NULL;
404405

405-
#define ADD_IF_INT_SET(args, argc, value) \
406-
if (value) { \
407-
args[argc] = nr_php_zval_alloc(); \
408-
ZVAL_LONG(args[argc], value); \
409-
argc++; \
410-
}
411-
412-
#define ADD_IF_STR_SET(args, argc, value) \
413-
if (value) { \
414-
args[argc] = nr_php_zval_alloc(); \
415-
nr_php_zval_str(args[argc], value); \
416-
argc++; \
417-
}
418-
419-
ADD_IF_STR_SET(argv, argc,
406+
#define ADD_IF_INT_SET(null_ok, args, argc, value) \
407+
if (value) { \
408+
args[argc] = nr_php_zval_alloc(); \
409+
ZVAL_LONG(args[argc], value); \
410+
argc++; \
411+
} else if (true == null_ok) { \
412+
args[argc] = nr_php_zval_alloc(); \
413+
ZVAL_NULL(args[argc]); \
414+
argc++; \
415+
}
416+
417+
#define ADD_IF_STR_SET(null_ok, args, argc, value) \
418+
if (value) { \
419+
args[argc] = nr_php_zval_alloc(); \
420+
nr_php_zval_str(args[argc], value); \
421+
argc++; \
422+
} else if (true == null_ok) { \
423+
args[argc] = nr_php_zval_alloc(); \
424+
ZVAL_NULL(args[argc]); \
425+
argc++; \
426+
}
427+
428+
ADD_IF_STR_SET(false, argv, argc,
420429
nr_php_mysqli_strip_persistent_prefix(metadata->host));
421-
ADD_IF_STR_SET(argv, argc, metadata->user);
422-
ADD_IF_STR_SET(argv, argc, metadata->password);
430+
ADD_IF_STR_SET(false, argv, argc, metadata->user);
431+
ADD_IF_STR_SET(false, argv, argc, metadata->password);
423432

424433
/*
425434
* We can only add the remaining metadata fields if we already have three
426435
* arguments (host, user and password) above, lest we accidentally set the
427-
* wrong positional argument to something it doesn't mean.
436+
* wrong positional argument to something it doesn't mean. Note, prior
437+
* to 7.4 not all args are nullable.
428438
*/
439+
arg_required = argc;
429440
if (argc == 3) {
430-
ADD_IF_STR_SET(argv, argc, metadata->database);
431-
ADD_IF_INT_SET(argv, argc, metadata->port);
432-
ADD_IF_STR_SET(argv, argc, metadata->socket);
433-
ADD_IF_INT_SET(argv, argc, metadata->flags);
434-
}
435-
436-
retval = nr_php_call_user_func(link, "real_connect", argc, argv TSRMLS_CC);
441+
ADD_IF_STR_SET(true, argv, argc, metadata->database);
442+
ADD_IF_INT_SET(true, argv, argc, metadata->port);
443+
ADD_IF_STR_SET(true, argv, argc, metadata->socket);
444+
ADD_IF_INT_SET(false, argv, argc, metadata->flags);
445+
} retval = nr_php_call_user_func(link, "real_connect", argc, argv TSRMLS_CC);
437446

438447
for (i = 0; i < argc; i++) {
439448
nr_php_zval_free(&argv[i]);
@@ -450,7 +459,7 @@ static nr_status_t nr_php_mysqli_link_real_connect(
450459
* If we didn't specify the database in the connection parameters, we need to
451460
* call mysqli::select_db here.
452461
*/
453-
if (metadata->database && (argc < 4)) {
462+
if (metadata->database && (arg_required < 3)) {
454463
zval* database = nr_php_zval_alloc();
455464

456465
nr_php_zval_str(database, metadata->database);

docker-compose.yaml

+9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ services:
2424
retries: 3
2525
start_period: 20s
2626
container_name: mysqldb
27+
volumes:
28+
- var-run-mysqld:/var/run/mysqld
2729
redisdb:
2830
image: redis
2931
restart: always
@@ -56,6 +58,7 @@ services:
5658
MYSQL_USER: admin
5759
MYSQL_PASSWD: admin
5860
MYSQL_HOST: mysqldb
61+
MYSQL_SOCKET: /var/run/mysqld/mysqld.sock
5962

6063
PG_HOST: postgres
6164
PG_PORT: 5432
@@ -67,6 +70,7 @@ services:
6770

6871
volumes:
6972
- ${AGENT_CODE:-$PWD}:/usr/local/src/newrelic-php-agent
73+
- var-run-mysqld:/var/run/mysqld
7074
entrypoint: tail
7175
command: -f /dev/null
7276
container_name: nr-php
@@ -83,6 +87,7 @@ services:
8387
MYSQL_USER: admin
8488
MYSQL_PASSWD: admin
8589
MYSQL_HOST: mysqldb
90+
MYSQL_SOCKET: /var/run/mysqld/mysqld.sock
8691

8792
PG_HOST: postgres
8893
PG_PORT: 5432
@@ -97,8 +102,12 @@ services:
97102
NEWRELIC_LICENSE_KEY: ${NEW_RELIC_LICENSE_KEY}
98103
volumes:
99104
- ${PWD}:/usr/src/myapp
105+
- var-run-mysqld:/var/run/mysqld
100106
working_dir: /usr/src/myapp
101107
stdin_open: true
102108
tty: true
103109
container_name: agent-devenv
104110
profiles: ["dev"]
111+
112+
volumes:
113+
var-run-mysqld:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
/*
3+
* Copyright 2020 New Relic Corporation. All rights reserved.
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/*DESCRIPTION
8+
The agent should generate explain plans when connections are made with
9+
mysqli_connect() when connecting to the database via socket.
10+
*/
11+
12+
/*SKIPIF
13+
<?php require("skipif.inc");
14+
*/
15+
16+
/*INI
17+
error_reporting = E_ALL & ~E_DEPRECATED
18+
newrelic.transaction_tracer.explain_enabled = true
19+
newrelic.transaction_tracer.explain_threshold = 0
20+
newrelic.transaction_tracer.record_sql = obfuscated
21+
*/
22+
23+
/*EXPECT
24+
STATISTICS
25+
*/
26+
27+
/*EXPECT_METRICS
28+
[
29+
"?? agent run id",
30+
"?? start time",
31+
"?? stop time",
32+
[
33+
[{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/all"}, [1, "??", "??", "??", "??", "??"]],
34+
[{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther"}, [1, "??", "??", "??", "??", "??"]],
35+
[{"name":"Datastore/all"}, [1, "??", "??", "??", "??", "??"]],
36+
[{"name":"Datastore/allOther"}, [1, "??", "??", "??", "??", "??"]],
37+
[{"name":"Datastore/MySQL/all"}, [1, "??", "??", "??", "??", "??"]],
38+
[{"name":"Datastore/MySQL/allOther"}, [1, "??", "??", "??", "??", "??"]],
39+
[{"name":"Datastore/statement/MySQL/tables/select"}, [1, "??", "??", "??", "??", "??"]],
40+
[{"name":"Datastore/statement/MySQL/tables/select",
41+
"scope":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
42+
[{"name":"Datastore/operation/MySQL/select"}, [1, "??", "??", "??", "??", "??"]],
43+
[{"name":"OtherTransaction/all"}, [1, "??", "??", "??", "??", "??"]],
44+
[{"name":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
45+
[{"name":"OtherTransactionTotalTime"}, [1, "??", "??", "??", "??", "??"]],
46+
[{"name":"OtherTransactionTotalTime/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
47+
[{"name":"Supportability/Logging/Forwarding/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]],
48+
[{"name":"Supportability/Logging/Metrics/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]],
49+
[{"name":"Supportability/Logging/LocalDecorating/PHP/disabled"}, [1, "??", "??", "??", "??", "??"]]
50+
]
51+
]
52+
*/
53+
54+
55+
56+
/*EXPECT_SLOW_SQLS
57+
[
58+
[
59+
[
60+
"OtherTransaction/php__FILE__",
61+
"<unknown>",
62+
"?? SQL ID",
63+
"SELECT TABLE_NAME FROM information_schema.tables WHERE table_name=?",
64+
"Datastore/statement/MySQL/tables/select",
65+
1,
66+
"?? total time",
67+
"?? min time",
68+
"?? max time",
69+
{
70+
"explain_plan": [
71+
[
72+
"id",
73+
"select_type",
74+
"table",
75+
"type",
76+
"possible_keys",
77+
"key",
78+
"key_len",
79+
"ref",
80+
"rows",
81+
"Extra"
82+
],
83+
[
84+
[
85+
1,
86+
"SIMPLE",
87+
"tables",
88+
"ALL",
89+
null,
90+
"TABLE_NAME",
91+
null,
92+
null,
93+
null,
94+
"Using where; Skip_open_table; Scanned 1 database"
95+
]
96+
]
97+
],
98+
"backtrace": [
99+
" in mysqli_stmt_execute called at __FILE__ (??)",
100+
" in test_prepare called at __FILE__ (??)"
101+
]
102+
}
103+
]
104+
]
105+
]
106+
*/
107+
108+
/*EXPECT_TRACED_ERRORS
109+
null
110+
*/
111+
112+
require_once(realpath (dirname ( __FILE__ )) . '/../../include/config.php');
113+
114+
function test_prepare($link)
115+
{
116+
$query = "SELECT TABLE_NAME FROM information_schema.tables WHERE table_name='STATISTICS'";
117+
118+
$stmt = mysqli_prepare($link, $query);
119+
if (FALSE === $stmt) {
120+
echo mysqli_error($link) . "\n";
121+
return;
122+
}
123+
124+
if (FALSE === mysqli_stmt_execute($stmt)) {
125+
echo mysqli_stmt_error($stmt) . "\n";
126+
return;
127+
}
128+
129+
if (FALSE === mysqli_stmt_bind_result($stmt, $value)) {
130+
echo mysqli_stmt_error($stmt) . "\n";
131+
return;
132+
}
133+
134+
while (mysqli_stmt_fetch($stmt)) {
135+
echo $value . "\n";
136+
}
137+
138+
mysqli_stmt_close($stmt);
139+
}
140+
141+
$link = mysqli_connect('localhost', $MYSQL_USER, $MYSQL_PASSWD, $MYSQL_DB, null, $MYSQL_SOCKET);
142+
if (mysqli_connect_errno()) {
143+
echo mysqli_connect_error() . "\n";
144+
exit(1);
145+
}
146+
147+
test_prepare($link);
148+
mysqli_close($link);

0 commit comments

Comments
 (0)