Skip to content

Commit ab4586a

Browse files
committed
Implement mysqli_execute_query()
1 parent 23b0257 commit ab4586a

File tree

5 files changed

+262
-1
lines changed

5 files changed

+262
-1
lines changed

ext/mysqli/mysqli.stub.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,11 @@ public function debug(string $options) {} // TODO make return type void
205205
*/
206206
public function get_charset(): ?object {}
207207

208+
/**
209+
* @alias mysqli_execute_query
210+
*/
211+
public function execute_query(string $query, ?array $params = null): mysqli_result|bool {}
212+
208213
/**
209214
* @tentative-return-type
210215
* @alias mysqli_get_client_info
@@ -787,6 +792,8 @@ function mysqli_stmt_execute(mysqli_stmt $statement, ?array $params = null): boo
787792
/** @alias mysqli_stmt_execute */
788793
function mysqli_execute(mysqli_stmt $statement, ?array $params = null): bool {}
789794

795+
function mysqli_execute_query(mysqli $mysql, string $query, ?array $params = null): mysqli_result|bool {}
796+
790797
/** @refcount 1 */
791798
function mysqli_fetch_field(mysqli_result $result): object|false {}
792799

ext/mysqli/mysqli_api.c

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,148 @@ PHP_FUNCTION(mysqli_stmt_execute)
476476
}
477477
/* }}} */
478478

479+
void close_stmt_and_copy_errors(MY_STMT *stmt, MY_MYSQL *mysql)
480+
{
481+
/* mysql_stmt_close() clears errors, so we have to store them temporarily */
482+
MYSQLND_ERROR_INFO error_info = *stmt->stmt->data->error_info;
483+
stmt->stmt->data->error_info->error_list.head = NULL;
484+
stmt->stmt->data->error_info->error_list.tail = NULL;
485+
stmt->stmt->data->error_info->error_list.count = 0;
486+
487+
/* we also remember affected_rows which gets cleared too */
488+
uint64_t affected_rows = mysql->mysql->data->upsert_status->affected_rows;
489+
490+
mysqli_stmt_close(stmt->stmt, false);
491+
stmt->stmt = NULL;
492+
php_clear_stmt_bind(stmt);
493+
494+
/* restore error messages, but into the mysql object */
495+
zend_llist_clean(&mysql->mysql->data->error_info->error_list);
496+
*mysql->mysql->data->error_info = error_info;
497+
mysql->mysql->data->upsert_status->affected_rows = affected_rows;
498+
}
499+
500+
PHP_FUNCTION(mysqli_execute_query)
501+
{
502+
MY_MYSQL *mysql;
503+
MY_STMT *stmt;
504+
char *query = NULL;
505+
size_t query_len;
506+
zval *mysql_link;
507+
HashTable *input_params = NULL;
508+
MYSQL_RES *result;
509+
MYSQLI_RESOURCE *mysqli_resource;
510+
511+
if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Os|h!", &mysql_link, mysqli_link_class_entry, &query, &query_len, &input_params) == FAILURE) {
512+
RETURN_THROWS();
513+
}
514+
MYSQLI_FETCH_RESOURCE_CONN(mysql, mysql_link, MYSQLI_STATUS_VALID);
515+
516+
stmt = (MY_STMT *)ecalloc(1,sizeof(MY_STMT));
517+
518+
if (!(stmt->stmt = mysql_stmt_init(mysql->mysql))) {
519+
MYSQLI_REPORT_MYSQL_ERROR(mysql->mysql);
520+
efree(stmt);
521+
RETURN_FALSE;
522+
}
523+
524+
if (FAIL == mysql_stmt_prepare(stmt->stmt, query, query_len)) {
525+
MYSQLI_REPORT_STMT_ERROR(stmt->stmt);
526+
527+
close_stmt_and_copy_errors(stmt, mysql);
528+
RETURN_FALSE;
529+
}
530+
531+
/* The bit below, which is copied from mysqli_stmt_prepare, is needed for bad index exceptions */
532+
/* don't initialize stmt->query with NULL, we ecalloc()-ed the memory */
533+
/* Get performance boost if reporting is switched off */
534+
if (query_len && (MyG(report_mode) & MYSQLI_REPORT_INDEX)) {
535+
stmt->query = (char *)emalloc(query_len + 1);
536+
memcpy(stmt->query, query, query_len);
537+
stmt->query[query_len] = '\0';
538+
}
539+
540+
// bind-in-execute
541+
// It's very similar to the mysqli_stmt::execute, but it uses different error handling
542+
if (input_params) {
543+
zval *tmp;
544+
unsigned int index;
545+
unsigned int hash_num_elements;
546+
unsigned int param_count;
547+
MYSQLND_PARAM_BIND *params;
548+
549+
if (!zend_array_is_list(input_params)) {
550+
mysqli_stmt_close(stmt->stmt, false);
551+
stmt->stmt = NULL;
552+
efree(stmt);
553+
zend_argument_value_error(ERROR_ARG_POS(3), "must be a list array");
554+
RETURN_THROWS();
555+
}
556+
557+
hash_num_elements = zend_hash_num_elements(input_params);
558+
param_count = mysql_stmt_param_count(stmt->stmt);
559+
if (hash_num_elements != param_count) {
560+
mysqli_stmt_close(stmt->stmt, false);
561+
stmt->stmt = NULL;
562+
efree(stmt);
563+
zend_argument_value_error(ERROR_ARG_POS(3), "must consist of exactly %d elements, %d present", param_count, hash_num_elements);
564+
RETURN_THROWS();
565+
}
566+
567+
params = mysqlnd_stmt_alloc_param_bind(stmt->stmt);
568+
ZEND_ASSERT(params);
569+
570+
index = 0;
571+
ZEND_HASH_FOREACH_VAL(input_params, tmp) {
572+
ZVAL_COPY_VALUE(&params[index].zv, tmp);
573+
params[index].type = MYSQL_TYPE_VAR_STRING;
574+
index++;
575+
} ZEND_HASH_FOREACH_END();
576+
577+
if (mysqlnd_stmt_bind_param(stmt->stmt, params)) {
578+
close_stmt_and_copy_errors(stmt, mysql);
579+
RETURN_FALSE;
580+
}
581+
582+
}
583+
584+
if (mysql_stmt_execute(stmt->stmt)) {
585+
MYSQLI_REPORT_STMT_ERROR(stmt->stmt);
586+
587+
if (MyG(report_mode) & MYSQLI_REPORT_INDEX) {
588+
php_mysqli_report_index(stmt->query, mysqli_stmt_server_status(stmt->stmt));
589+
}
590+
591+
close_stmt_and_copy_errors(stmt, mysql);
592+
RETURN_FALSE;
593+
}
594+
595+
if (!mysql_stmt_field_count(stmt->stmt)) {
596+
/* no result set - not a SELECT */
597+
close_stmt_and_copy_errors(stmt, mysql);
598+
RETURN_TRUE;
599+
}
600+
601+
if (MyG(report_mode) & MYSQLI_REPORT_INDEX) {
602+
php_mysqli_report_index(stmt->query, mysqli_stmt_server_status(stmt->stmt));
603+
}
604+
605+
/* get result */
606+
if (!(result = mysqlnd_stmt_get_result(stmt->stmt))) {
607+
MYSQLI_REPORT_STMT_ERROR(stmt->stmt);
608+
609+
close_stmt_and_copy_errors(stmt, mysql);
610+
RETURN_FALSE;
611+
}
612+
613+
mysqli_resource = (MYSQLI_RESOURCE *)ecalloc (1, sizeof(MYSQLI_RESOURCE));
614+
mysqli_resource->ptr = (void *)result;
615+
mysqli_resource->status = MYSQLI_STATUS_VALID;
616+
MYSQLI_RETVAL_RESOURCE(mysqli_resource, mysqli_result_class_entry);
617+
618+
close_stmt_and_copy_errors(stmt, mysql);
619+
}
620+
479621
/* {{{ mixed mysqli_stmt_fetch_mysqlnd */
480622
void mysqli_stmt_fetch_mysqlnd(INTERNAL_FUNCTION_PARAMETERS)
481623
{

ext/mysqli/mysqli_arginfo.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* This is a generated file, edit the .stub.php file instead.
2-
* Stub hash: a2f2992afd959a13215bfdfd00096f368b3bc392 */
2+
* Stub hash: 98dd6ac047c832eb47e3bb20bb6c42c6331d599f */
33

44
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_mysqli_affected_rows, 0, 1, MAY_BE_LONG|MAY_BE_STRING)
55
ZEND_ARG_OBJ_INFO(0, mysql, mysqli, 0)
@@ -76,6 +76,12 @@ ZEND_END_ARG_INFO()
7676

7777
#define arginfo_mysqli_execute arginfo_mysqli_stmt_execute
7878

79+
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_mysqli_execute_query, 0, 2, mysqli_result, MAY_BE_BOOL)
80+
ZEND_ARG_OBJ_INFO(0, mysql, mysqli, 0)
81+
ZEND_ARG_TYPE_INFO(0, query, IS_STRING, 0)
82+
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, params, IS_ARRAY, 1, "null")
83+
ZEND_END_ARG_INFO()
84+
7985
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_mysqli_fetch_field, 0, 1, MAY_BE_OBJECT|MAY_BE_FALSE)
8086
ZEND_ARG_OBJ_INFO(0, result, mysqli_result, 0)
8187
ZEND_END_ARG_INFO()
@@ -454,6 +460,11 @@ ZEND_END_ARG_INFO()
454460
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_mysqli_get_charset, 0, 0, IS_OBJECT, 1)
455461
ZEND_END_ARG_INFO()
456462

463+
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_mysqli_execute_query, 0, 1, mysqli_result, MAY_BE_BOOL)
464+
ZEND_ARG_TYPE_INFO(0, query, IS_STRING, 0)
465+
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, params, IS_ARRAY, 1, "null")
466+
ZEND_END_ARG_INFO()
467+
457468
#define arginfo_class_mysqli_get_client_info arginfo_class_mysqli_character_set_name
458469

459470
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_mysqli_get_connection_stats, 0, 0, IS_ARRAY, 0)
@@ -709,6 +720,7 @@ ZEND_FUNCTION(mysqli_errno);
709720
ZEND_FUNCTION(mysqli_error);
710721
ZEND_FUNCTION(mysqli_error_list);
711722
ZEND_FUNCTION(mysqli_stmt_execute);
723+
ZEND_FUNCTION(mysqli_execute_query);
712724
ZEND_FUNCTION(mysqli_fetch_field);
713725
ZEND_FUNCTION(mysqli_fetch_fields);
714726
ZEND_FUNCTION(mysqli_fetch_field_direct);
@@ -823,6 +835,7 @@ static const zend_function_entry ext_functions[] = {
823835
ZEND_FE(mysqli_error_list, arginfo_mysqli_error_list)
824836
ZEND_FE(mysqli_stmt_execute, arginfo_mysqli_stmt_execute)
825837
ZEND_FALIAS(mysqli_execute, mysqli_stmt_execute, arginfo_mysqli_execute)
838+
ZEND_FE(mysqli_execute_query, arginfo_mysqli_execute_query)
826839
ZEND_FE(mysqli_fetch_field, arginfo_mysqli_fetch_field)
827840
ZEND_FE(mysqli_fetch_fields, arginfo_mysqli_fetch_fields)
828841
ZEND_FE(mysqli_fetch_field_direct, arginfo_mysqli_fetch_field_direct)
@@ -931,6 +944,7 @@ static const zend_function_entry class_mysqli_methods[] = {
931944
ZEND_ME_MAPPING(dump_debug_info, mysqli_dump_debug_info, arginfo_class_mysqli_dump_debug_info, ZEND_ACC_PUBLIC)
932945
ZEND_ME_MAPPING(debug, mysqli_debug, arginfo_class_mysqli_debug, ZEND_ACC_PUBLIC)
933946
ZEND_ME_MAPPING(get_charset, mysqli_get_charset, arginfo_class_mysqli_get_charset, ZEND_ACC_PUBLIC)
947+
ZEND_ME_MAPPING(execute_query, mysqli_execute_query, arginfo_class_mysqli_execute_query, ZEND_ACC_PUBLIC)
934948
ZEND_ME_MAPPING(get_client_info, mysqli_get_client_info, arginfo_class_mysqli_get_client_info, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED)
935949
ZEND_ME_MAPPING(get_connection_stats, mysqli_get_connection_stats, arginfo_class_mysqli_get_connection_stats, ZEND_ACC_PUBLIC)
936950
ZEND_ME_MAPPING(get_server_info, mysqli_get_server_info, arginfo_class_mysqli_get_server_info, ZEND_ACC_PUBLIC)

ext/mysqli/tests/mysqli_class_mysqli_interface.phpt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ require_once('skipifconnectfailure.inc');
2929
'connect' => true,
3030
'dump_debug_info' => true,
3131
'escape_string' => true,
32+
'execute_query' => true,
3233
'get_charset' => true,
3334
'get_client_info' => true,
3435
'get_server_info' => true,
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
--TEST--
2+
mysqli_execute_query()
3+
--EXTENSIONS--
4+
mysqli
5+
--SKIPIF--
6+
<?php
7+
require_once 'skipifconnectfailure.inc';
8+
?>
9+
--FILE--
10+
<?php
11+
12+
require 'table.inc';
13+
14+
if (!($tmp = mysqli_execute_query($link, "SELECT id, label FROM test ORDER BY id"))) {
15+
printf("[001] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
16+
}
17+
18+
if (!is_a($tmp, mysqli_result::class)) {
19+
printf("[002] Expecting mysqli_result, got %s/%s\n", gettype($tmp), $tmp);
20+
}
21+
22+
unset($tmp);
23+
24+
// procedural
25+
if (!($tmp = mysqli_execute_query($link, "SELECT ? AS a, ? AS b, ? AS c", [42, "foo", null]))) {
26+
printf("[003] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
27+
}
28+
29+
assert($tmp->fetch_assoc() === ['a' => '42', 'b' => 'foo', 'c' => null]);
30+
31+
// OO style
32+
if (!($tmp = $link->execute_query("SELECT ? AS a, ? AS b, ? AS c", [42, "foo", null]))) {
33+
printf("[004] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
34+
}
35+
36+
assert($tmp->fetch_assoc() === ['a' => '42', 'b' => 'foo', 'c' => null]);
37+
38+
// prepare error
39+
if (!($tmp = $link->execute_query("some random gibberish", [1, "foo"]))) {
40+
printf("[005] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
41+
}
42+
43+
// stmt error - duplicate key
44+
if (!$link->execute_query("INSERT INTO test(id, label) VALUES (?, ?)", [1, "foo"])) {
45+
printf("[006] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
46+
}
47+
48+
// successful update returns true
49+
if (!($tmp = $link->execute_query("UPDATE test SET label=? WHERE id=?", ["baz", 1]))) {
50+
printf("[007] Expecting true, got %s/%s\n", gettype($tmp), $tmp);
51+
}
52+
if ($link->affected_rows <= 0) {
53+
printf("[008] Expecting positive non-zero integer for affected_rows, got %s/%s\n", gettype($link->affected_rows), $link->affected_rows);
54+
}
55+
56+
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
57+
58+
// check if value error is properly reported
59+
try {
60+
$link->execute_query('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?', ['foo', 42]);
61+
} catch (ValueError $e) {
62+
echo '[009] '.$e->getMessage()."\n";
63+
}
64+
try {
65+
$link->execute_query('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?', ['foo' => 42]);
66+
} catch (ValueError $e) {
67+
echo '[010] '.$e->getMessage()."\n";
68+
}
69+
70+
// check if insert_id is copied
71+
$link->execute_query("ALTER TABLE test MODIFY id INT NOT NULL AUTO_INCREMENT");
72+
$link->execute_query("INSERT INTO test(label) VALUES (?)", ["foo"]);
73+
if ($link->insert_id <= 0) {
74+
printf("[011] Expecting positive non-zero integer for insert_id, got %s/%s\n", gettype($link->insert_id), $link->insert_id);
75+
}
76+
77+
// bad index
78+
mysqli_report(MYSQLI_REPORT_ALL);
79+
try {
80+
$link->execute_query("SELECT id FROM test WHERE label = ?", ["foo"]);
81+
} catch (mysqli_sql_exception $e) {
82+
echo '[012] '.$e->getMessage()."\n";
83+
}
84+
85+
print "done!";
86+
?>
87+
--CLEAN--
88+
<?php
89+
require_once "clean_table.inc";
90+
?>
91+
--EXPECTF--
92+
[005] [1064] You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'some random gibberish' at line 1
93+
[006] [1062] Duplicate entry '1' for key '%s'
94+
[009] mysqli::execute_query(): Argument #2 ($params) must consist of exactly 3 elements, 2 present
95+
[010] mysqli::execute_query(): Argument #2 ($params) must be a list array
96+
[012] No index used in query/prepared statement SELECT id FROM test WHERE label = ?
97+
done!

0 commit comments

Comments
 (0)