mirror of
https://github.com/php/php-src.git
synced 2024-09-22 02:17:32 +00:00
Implement mysqli_execute_query() (#8660)
This commit is contained in:
parent
b45cd10238
commit
1dc51c7b90
@ -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 {}
|
||||
|
||||
|
@ -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(¶ms[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 */
|
||||
|
16
ext/mysqli/mysqli_arginfo.h
generated
16
ext/mysqli/mysqli_arginfo.h
generated
@ -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)
|
||||
|
@ -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,
|
||||
|
97
ext/mysqli/tests/mysqli_execute_query.phpt
Normal file
97
ext/mysqli/tests/mysqli_execute_query.phpt
Normal 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!
|
Loading…
Reference in New Issue
Block a user