Skip to content

Implement mysqli_execute_query() #8660

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 6, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 7 additions & 0 deletions ext/mysqli/mysqli.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ public function debug(string $options) {} // TODO make return type void
*/
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
Expand Down Expand Up @@ -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 {}

Expand Down
144 changes: 141 additions & 3 deletions ext/mysqli/mysqli_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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 */
Expand Down
16 changes: 15 additions & 1 deletion ext/mysqli/mysqli_arginfo.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions ext/mysqli/tests/mysqli_class_mysqli_interface.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
97 changes: 97 additions & 0 deletions ext/mysqli/tests/mysqli_execute_query.phpt
Original file line number Diff line number Diff line change
@@ -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!