Implement mysqli_execute_query() (#8660)

This commit is contained in:
Kamil Tekiela 2022-07-06 12:23:46 +01:00 committed by GitHub
parent b45cd10238
commit 1dc51c7b90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 261 additions and 4 deletions

View File

@ -208,6 +208,11 @@ class mysqli
*/
public function get_charset(): ?object {}
/**
* @alias mysqli_execute_query
*/
public function execute_query(string $query, ?array $params = null): mysqli_result|bool {}
/**
* @tentative-return-type
* @alias mysqli_get_client_info
@ -793,6 +798,8 @@ function mysqli_stmt_execute(mysqli_stmt $statement, ?array $params = null): boo
/** @alias mysqli_stmt_execute */
function mysqli_execute(mysqli_stmt $statement, ?array $params = null): bool {}
function mysqli_execute_query(mysqli $mysql, string $query, ?array $params = null): mysqli_result|bool {}
/** @refcount 1 */
function mysqli_fetch_field(mysqli_result $result): object|false {}

View File

@ -476,6 +476,146 @@ PHP_FUNCTION(mysqli_stmt_execute)
}
/* }}} */
void close_stmt_and_copy_errors(MY_STMT *stmt, MY_MYSQL *mysql)
{
/* mysql_stmt_close() clears errors, so we have to store them temporarily */
MYSQLND_ERROR_INFO error_info = *stmt->stmt->data->error_info;
stmt->stmt->data->error_info->error_list.head = NULL;
stmt->stmt->data->error_info->error_list.tail = NULL;
stmt->stmt->data->error_info->error_list.count = 0;
/* we also remember affected_rows which gets cleared too */
uint64_t affected_rows = mysql->mysql->data->upsert_status->affected_rows;
mysqli_stmt_close(stmt->stmt, false);
stmt->stmt = NULL;
php_clear_stmt_bind(stmt);
/* restore error messages, but into the mysql object */
zend_llist_clean(&mysql->mysql->data->error_info->error_list);
*mysql->mysql->data->error_info = error_info;
mysql->mysql->data->upsert_status->affected_rows = affected_rows;
}
PHP_FUNCTION(mysqli_execute_query)
{
MY_MYSQL *mysql;
MY_STMT *stmt;
char *query = NULL;
size_t query_len;
zval *mysql_link;
HashTable *input_params = NULL;
MYSQL_RES *result;
MYSQLI_RESOURCE *mysqli_resource;
if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Os|h!", &mysql_link, mysqli_link_class_entry, &query, &query_len, &input_params) == FAILURE) {
RETURN_THROWS();
}
MYSQLI_FETCH_RESOURCE_CONN(mysql, mysql_link, MYSQLI_STATUS_VALID);
stmt = (MY_STMT *)ecalloc(1,sizeof(MY_STMT));
if (!(stmt->stmt = mysql_stmt_init(mysql->mysql))) {
MYSQLI_REPORT_MYSQL_ERROR(mysql->mysql);
efree(stmt);
RETURN_FALSE;
}
if (FAIL == mysql_stmt_prepare(stmt->stmt, query, query_len)) {
MYSQLI_REPORT_STMT_ERROR(stmt->stmt);
close_stmt_and_copy_errors(stmt, mysql);
RETURN_FALSE;
}
/* The bit below, which is copied from mysqli_prepare, is needed for bad index exceptions */
/* don't initialize stmt->query with NULL, we ecalloc()-ed the memory */
/* Get performance boost if reporting is switched off */
if (query_len && (MyG(report_mode) & MYSQLI_REPORT_INDEX)) {
stmt->query = estrdup(query);
}
// bind-in-execute
// It's very similar to the mysqli_stmt::execute, but it uses different error handling
if (input_params) {
zval *tmp;
unsigned int index;
unsigned int hash_num_elements;
unsigned int param_count;
MYSQLND_PARAM_BIND *params;
if (!zend_array_is_list(input_params)) {
mysqli_stmt_close(stmt->stmt, false);
stmt->stmt = NULL;
efree(stmt);
zend_argument_value_error(ERROR_ARG_POS(3), "must be a list array");
RETURN_THROWS();
}
hash_num_elements = zend_hash_num_elements(input_params);
param_count = mysql_stmt_param_count(stmt->stmt);
if (hash_num_elements != param_count) {
mysqli_stmt_close(stmt->stmt, false);
stmt->stmt = NULL;
efree(stmt);
zend_argument_value_error(ERROR_ARG_POS(3), "must consist of exactly %d elements, %d present", param_count, hash_num_elements);
RETURN_THROWS();
}
params = mysqlnd_stmt_alloc_param_bind(stmt->stmt);
ZEND_ASSERT(params);
index = 0;
ZEND_HASH_FOREACH_VAL(input_params, tmp) {
ZVAL_COPY_VALUE(&params[index].zv, tmp);
params[index].type = MYSQL_TYPE_VAR_STRING;
index++;
} ZEND_HASH_FOREACH_END();
if (mysqlnd_stmt_bind_param(stmt->stmt, params)) {
close_stmt_and_copy_errors(stmt, mysql);
RETURN_FALSE;
}
}
if (mysql_stmt_execute(stmt->stmt)) {
MYSQLI_REPORT_STMT_ERROR(stmt->stmt);
if (MyG(report_mode) & MYSQLI_REPORT_INDEX) {
php_mysqli_report_index(stmt->query, mysqli_stmt_server_status(stmt->stmt));
}
close_stmt_and_copy_errors(stmt, mysql);
RETURN_FALSE;
}
if (!mysql_stmt_field_count(stmt->stmt)) {
/* no result set - not a SELECT */
close_stmt_and_copy_errors(stmt, mysql);
RETURN_TRUE;
}
if (MyG(report_mode) & MYSQLI_REPORT_INDEX) {
php_mysqli_report_index(stmt->query, mysqli_stmt_server_status(stmt->stmt));
}
/* get result */
if (!(result = mysqlnd_stmt_get_result(stmt->stmt))) {
MYSQLI_REPORT_STMT_ERROR(stmt->stmt);
close_stmt_and_copy_errors(stmt, mysql);
RETURN_FALSE;
}
mysqli_resource = (MYSQLI_RESOURCE *)ecalloc (1, sizeof(MYSQLI_RESOURCE));
mysqli_resource->ptr = (void *)result;
mysqli_resource->status = MYSQLI_STATUS_VALID;
MYSQLI_RETVAL_RESOURCE(mysqli_resource, mysqli_result_class_entry);
close_stmt_and_copy_errors(stmt, mysql);
}
/* {{{ mixed mysqli_stmt_fetch_mysqlnd */
void mysqli_stmt_fetch_mysqlnd(INTERNAL_FUNCTION_PARAMETERS)
{
@ -1188,9 +1328,7 @@ PHP_FUNCTION(mysqli_prepare)
/* don't initialize stmt->query with NULL, we ecalloc()-ed the memory */
/* Get performance boost if reporting is switched off */
if (stmt->stmt && query_len && (MyG(report_mode) & MYSQLI_REPORT_INDEX)) {
stmt->query = (char *)emalloc(query_len + 1);
memcpy(stmt->query, query, query_len);
stmt->query[query_len] = '\0';
stmt->query = estrdup(query);
}
/* don't join to the previous if because it won't work if mysql_stmt_prepare_fails */

View File

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: e528bb1e05a85d3d764272c5f3f4256d2608da6c */
* Stub hash: 2dae4302d825a7f5da3c4e00ce87aebc5502a8af */
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_mysqli_affected_rows, 0, 1, MAY_BE_LONG|MAY_BE_STRING)
ZEND_ARG_OBJ_INFO(0, mysql, mysqli, 0)
@ -78,6 +78,12 @@ ZEND_END_ARG_INFO()
#define arginfo_mysqli_execute arginfo_mysqli_stmt_execute
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_mysqli_execute_query, 0, 2, mysqli_result, MAY_BE_BOOL)
ZEND_ARG_OBJ_INFO(0, mysql, mysqli, 0)
ZEND_ARG_TYPE_INFO(0, query, IS_STRING, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, params, IS_ARRAY, 1, "null")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_mysqli_fetch_field, 0, 1, MAY_BE_OBJECT|MAY_BE_FALSE)
ZEND_ARG_OBJ_INFO(0, result, mysqli_result, 0)
ZEND_END_ARG_INFO()
@ -458,6 +464,11 @@ ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_mysqli_get_charset, 0, 0, IS_OBJECT, 1)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_class_mysqli_execute_query, 0, 1, mysqli_result, MAY_BE_BOOL)
ZEND_ARG_TYPE_INFO(0, query, IS_STRING, 0)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, params, IS_ARRAY, 1, "null")
ZEND_END_ARG_INFO()
#define arginfo_class_mysqli_get_client_info arginfo_class_mysqli_character_set_name
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_mysqli_get_connection_stats, 0, 0, IS_ARRAY, 0)
@ -713,6 +724,7 @@ ZEND_FUNCTION(mysqli_errno);
ZEND_FUNCTION(mysqli_error);
ZEND_FUNCTION(mysqli_error_list);
ZEND_FUNCTION(mysqli_stmt_execute);
ZEND_FUNCTION(mysqli_execute_query);
ZEND_FUNCTION(mysqli_fetch_field);
ZEND_FUNCTION(mysqli_fetch_fields);
ZEND_FUNCTION(mysqli_fetch_field_direct);
@ -827,6 +839,7 @@ static const zend_function_entry ext_functions[] = {
ZEND_FE(mysqli_error_list, arginfo_mysqli_error_list)
ZEND_FE(mysqli_stmt_execute, arginfo_mysqli_stmt_execute)
ZEND_FALIAS(mysqli_execute, mysqli_stmt_execute, arginfo_mysqli_execute)
ZEND_FE(mysqli_execute_query, arginfo_mysqli_execute_query)
ZEND_FE(mysqli_fetch_field, arginfo_mysqli_fetch_field)
ZEND_FE(mysqli_fetch_fields, arginfo_mysqli_fetch_fields)
ZEND_FE(mysqli_fetch_field_direct, arginfo_mysqli_fetch_field_direct)
@ -935,6 +948,7 @@ static const zend_function_entry class_mysqli_methods[] = {
ZEND_ME_MAPPING(dump_debug_info, mysqli_dump_debug_info, arginfo_class_mysqli_dump_debug_info, ZEND_ACC_PUBLIC)
ZEND_ME_MAPPING(debug, mysqli_debug, arginfo_class_mysqli_debug, ZEND_ACC_PUBLIC)
ZEND_ME_MAPPING(get_charset, mysqli_get_charset, arginfo_class_mysqli_get_charset, ZEND_ACC_PUBLIC)
ZEND_ME_MAPPING(execute_query, mysqli_execute_query, arginfo_class_mysqli_execute_query, ZEND_ACC_PUBLIC)
ZEND_ME_MAPPING(get_client_info, mysqli_get_client_info, arginfo_class_mysqli_get_client_info, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED)
ZEND_ME_MAPPING(get_connection_stats, mysqli_get_connection_stats, arginfo_class_mysqli_get_connection_stats, ZEND_ACC_PUBLIC)
ZEND_ME_MAPPING(get_server_info, mysqli_get_server_info, arginfo_class_mysqli_get_server_info, ZEND_ACC_PUBLIC)

View File

@ -29,6 +29,7 @@ require_once('skipifconnectfailure.inc');
'connect' => true,
'dump_debug_info' => true,
'escape_string' => true,
'execute_query' => true,
'get_charset' => true,
'get_client_info' => true,
'get_server_info' => true,

View File

@ -0,0 +1,97 @@
--TEST--
mysqli_execute_query()
--EXTENSIONS--
mysqli
--SKIPIF--
<?php
require_once 'skipifconnectfailure.inc';
?>
--FILE--
<?php
require 'table.inc';
if (!($tmp = mysqli_execute_query($link, "SELECT id, label FROM test ORDER BY id"))) {
printf("[001] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
}
if (!is_a($tmp, mysqli_result::class)) {
printf("[002] Expecting mysqli_result, got %s/%s\n", gettype($tmp), $tmp);
}
unset($tmp);
// procedural
if (!($tmp = mysqli_execute_query($link, "SELECT ? AS a, ? AS b, ? AS c", [42, "foo", null]))) {
printf("[003] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
}
assert($tmp->fetch_assoc() === ['a' => '42', 'b' => 'foo', 'c' => null]);
// OO style
if (!($tmp = $link->execute_query("SELECT ? AS a, ? AS b, ? AS c", [42, "foo", null]))) {
printf("[004] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
}
assert($tmp->fetch_assoc() === ['a' => '42', 'b' => 'foo', 'c' => null]);
// prepare error
if (!($tmp = $link->execute_query("some random gibberish", [1, "foo"]))) {
printf("[005] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
}
// stmt error - duplicate key
if (!$link->execute_query("INSERT INTO test(id, label) VALUES (?, ?)", [1, "foo"])) {
printf("[006] [%d] %s\n", mysqli_errno($link), mysqli_error($link));
}
// successful update returns true
if (!($tmp = $link->execute_query("UPDATE test SET label=? WHERE id=?", ["baz", 1]))) {
printf("[007] Expecting true, got %s/%s\n", gettype($tmp), $tmp);
}
if ($link->affected_rows <= 0) {
printf("[008] Expecting positive non-zero integer for affected_rows, got %s/%s\n", gettype($link->affected_rows), $link->affected_rows);
}
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
// check if value error is properly reported
try {
$link->execute_query('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?', ['foo', 42]);
} catch (ValueError $e) {
echo '[009] '.$e->getMessage()."\n";
}
try {
$link->execute_query('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?', ['foo' => 42]);
} catch (ValueError $e) {
echo '[010] '.$e->getMessage()."\n";
}
// check if insert_id is copied
$link->execute_query("ALTER TABLE test MODIFY id INT NOT NULL AUTO_INCREMENT");
$link->execute_query("INSERT INTO test(label) VALUES (?)", ["foo"]);
if ($link->insert_id <= 0) {
printf("[011] Expecting positive non-zero integer for insert_id, got %s/%s\n", gettype($link->insert_id), $link->insert_id);
}
// bad index
mysqli_report(MYSQLI_REPORT_ALL);
try {
$link->execute_query("SELECT id FROM test WHERE label = ?", ["foo"]);
} catch (mysqli_sql_exception $e) {
echo '[012] '.$e->getMessage()."\n";
}
print "done!";
?>
--CLEAN--
<?php
require_once "clean_table.inc";
?>
--EXPECTF--
[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
[006] [1062] Duplicate entry '1' for key '%s'
[009] mysqli::execute_query(): Argument #2 ($params) must consist of exactly 3 elements, 2 present
[010] mysqli::execute_query(): Argument #2 ($params) must be a list array
[012] No index used in query/prepared statement SELECT id FROM test WHERE label = ?
done!