Skip to content

Mysqli bind-in-execute libmysql support #7175

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

Closed
Closed
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
49 changes: 41 additions & 8 deletions ext/mysqli/mysqli_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -812,9 +812,6 @@ PHP_FUNCTION(mysqli_stmt_execute)
MY_STMT *stmt;
zval *mysql_stmt;
HashTable *input_params = NULL;
#ifndef MYSQLI_USE_MYSQLND
unsigned int i;
#endif

if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O|h!", &mysql_stmt, mysqli_stmt_class_entry, &input_params) == FAILURE) {
RETURN_THROWS();
Expand All @@ -823,12 +820,10 @@ PHP_FUNCTION(mysqli_stmt_execute)

// bind-in-execute
if (input_params) {
#if defined(MYSQLI_USE_MYSQLND)
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)) {
zend_argument_value_error(ERROR_ARG_POS(2), "must be a list array");
Expand All @@ -842,6 +837,8 @@ PHP_FUNCTION(mysqli_stmt_execute)
RETURN_THROWS();
}

#if defined(MYSQLI_USE_MYSQLND)
MYSQLND_PARAM_BIND *params;
params = mysqlnd_stmt_alloc_param_bind(stmt->stmt);
ZEND_ASSERT(params);

Expand All @@ -854,15 +851,51 @@ PHP_FUNCTION(mysqli_stmt_execute)

if (mysqlnd_stmt_bind_param(stmt->stmt, params)) {
MYSQLI_REPORT_STMT_ERROR(stmt->stmt);
RETVAL_FALSE;
RETURN_FALSE;
}
#else
zend_argument_count_error("Binding parameters in execute is not supported with libmysqlclient");
RETURN_THROWS();
MYSQL_BIND *bind;
unsigned long rc;

/* prevent leak if variables are already bound */
if (stmt->param.var_cnt) {
php_free_stmt_bind_buffer(stmt->param, FETCH_SIMPLE);
}

stmt->param.is_null = ecalloc(hash_num_elements, sizeof(char));
bind = (MYSQL_BIND *) ecalloc(hash_num_elements, sizeof(MYSQL_BIND));

for (index = 0; index < hash_num_elements; index++) {
/* set specified type */
bind[index].buffer_type = MYSQL_TYPE_VAR_STRING;
/* don't initialize buffer and buffer_length because we use ecalloc */
bind[index].is_null = &stmt->param.is_null[index];

index++;
}
rc = mysql_stmt_bind_param(stmt->stmt, bind);
if (rc) {
efree(stmt->param.is_null);
efree(bind);
MYSQLI_REPORT_STMT_ERROR(stmt->stmt);
RETURN_FALSE;
}

stmt->param.var_cnt = hash_num_elements;
stmt->param.vars = safe_emalloc(hash_num_elements, sizeof(zval), 0);
index = 0;
ZEND_HASH_FOREACH_VAL(input_params, tmp) {
ZVAL_COPY_VALUE(&stmt->param.vars[index], tmp);
index++;
} ZEND_HASH_FOREACH_END();

efree(bind);
#endif
}

#ifndef MYSQLI_USE_MYSQLND
unsigned int i;

if (stmt->param.var_cnt) {
int j;
for (i = 0; i < stmt->param.var_cnt; i++) {
Expand Down
129 changes: 127 additions & 2 deletions ext/mysqli/tests/mysqli_stmt_execute_bind_libmysql.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,148 @@ $abc = 'abc';
$stmt = $link->prepare('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?');
$stmt->bind_param('sss', ...[&$abc, 42, $id]);
$stmt->execute();
assert($stmt->get_result()->fetch_assoc() === ['label'=>'a', 'anon'=>'abc', 'num' => '42']);
$stmt->bind_result($v1, $v2, $v3);
$stmt->fetch();
assert(['label'=>$v1, 'anon'=>$v2, 'num'=>$v3] === ['label'=>'a', 'anon'=>'abc', 'num'=>'42']);
unset($v1, $v2, $v3);
$stmt = null;

// 1. same as the control case, but skipping the middle-man (bind_param)
$stmt = $link->prepare('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?');
try {
$stmt->execute([&$abc, 42, $id]);
$stmt->bind_result($v1, $v2, $v3);
assert(['label'=>$v1, 'anon'=>$v2, 'num'=>$v3] === ['label'=>null, 'anon'=>null, 'num'=>null]);
} catch (ArgumentCountError $e) {
echo '[001] '.$e->getMessage()."\n";
}
$stmt = null;

// 2. param number has to match - missing 1 parameter
$stmt = $link->prepare('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?');
try {
$stmt->execute([&$abc, 42]);
} catch (ValueError $e) {
echo '[001] '.$e->getMessage()."\n";
}
$stmt = null;

// 3. Too many parameters
$stmt = $link->prepare('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?');
try {
$stmt->execute([&$abc, null, $id, 24]);
} catch (ValueError $e) {
echo '[002] '.$e->getMessage()."\n";
}
$stmt = null;

// 4. param number has to match - missing all parameters
$stmt = $link->prepare('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?');
try {
$stmt->execute([]);
} catch (ValueError $e) {
echo '[003] '.$e->getMessage()."\n";
}
$stmt = null;

// 5. param number has to match - missing argument to execute()
$stmt = $link->prepare('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?');
try {
$stmt->execute();
} catch (mysqli_sql_exception $e) {
echo '[004] '.$e->getMessage()."\n";
}
$stmt = null;

// 6. wrong argument to execute()
$stmt = $link->prepare('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?');
try {
$stmt->execute(42);
} catch (TypeError $e) {
echo '[005] '.$e->getMessage()."\n";
}
$stmt = null;

// 7. objects are not arrays and are not accepted
$stmt = $link->prepare('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?');
try {
$stmt->execute((object)[&$abc, 42, $id]);
} catch (TypeError $e) {
echo '[006] '.$e->getMessage()."\n";
}
$stmt = null;

// 8. arrays by reference work too
$stmt = $link->prepare('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?');
$arr = [&$abc, 42, $id];
$arr2 = &$arr;
$stmt->execute($arr2);
$stmt->bind_result($v1, $v2, $v3);
$stmt->fetch();
assert(['label'=>$v1, 'anon'=>$v2, 'num'=>$v3] === ['label'=>'a', 'anon'=>'abc', 'num'=>'42']);
unset($v1, $v2, $v3);
$stmt = null;

// 9. no placeholders in statement. nothing to bind in an empty array
$stmt = $link->prepare('SELECT label FROM test WHERE id=1');
$stmt->execute([]);
$stmt->bind_result($v1);
$stmt->fetch();
assert(['label'=>$v1] === ['label'=>'a']);
unset($v1);
$stmt = null;

// 10. once bound the values are persisted. Just like in PDO
$stmt = $link->prepare('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?');
$stmt->execute(['abc', 42, $id]);
$stmt->bind_result($v1, $v2, $v3);
$stmt->fetch();
assert(['label'=>$v1, 'anon'=>$v2, 'num'=>$v3] === ['label'=>'a', 'anon'=>'abc', 'num'=>'42']);
unset($v1, $v2, $v3);
$stmt->execute(); // no argument here. Values are already bound
$stmt->bind_result($v1, $v2, $v3);
$stmt->fetch();
assert(['label'=>$v1, 'anon'=>$v2, 'num'=>$v3] === ['label'=>'a', 'anon'=>'abc', 'num'=>'42']);
unset($v1, $v2, $v3);
try {
$stmt->execute([]); // no params here. PDO doesn't throw an error, but mysqli does
} catch (ValueError $e) {
echo '[007] '.$e->getMessage()."\n";
}
$stmt = null;

// 11. mixing binding styles not possible. Also, NULL should stay NULL when bound as string
$stmt = $link->prepare('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?');
$stmt->bind_param('sss', ...['abc', 42, null]);
$stmt->execute([null, null, $id]);
$stmt->bind_result($v1, $v2, $v3);
$stmt->fetch();
assert(['label'=>$v1, 'anon'=>$v2, 'num'=>$v3] === ['label'=>'a', 'anon'=>null, 'num' => null]);
unset($v1, $v2, $v3);
$stmt = null;

// 12. Only list arrays are allowed
$stmt = $link->prepare('SELECT label, ? AS anon, ? AS num FROM test WHERE id=?');
try {
$stmt->execute(['A'=>'abc', 2=>42, null=>$id]);
} catch (ValueError $e) {
echo '[008] '.$e->getMessage()."\n";
}
$stmt = null;

mysqli_close($link);
?>
--CLEAN--
<?php
require_once "clean_table.inc";
?>
--EXPECT--
[001] Binding parameters in execute is not supported with libmysqlclient
[001] mysqli_stmt::execute(): Argument #1 ($params) must consist of exactly 3 elements, 2 present
[002] mysqli_stmt::execute(): Argument #1 ($params) must consist of exactly 3 elements, 4 present
[003] mysqli_stmt::execute(): Argument #1 ($params) must consist of exactly 3 elements, 0 present
[004] No data supplied for parameters in prepared statement
[005] mysqli_stmt::execute(): Argument #1 ($params) must be of type ?array, int given
[006] mysqli_stmt::execute(): Argument #1 ($params) must be of type ?array, stdClass given
[007] mysqli_stmt::execute(): Argument #1 ($params) must consist of exactly 3 elements, 0 present
[008] mysqli_stmt::execute(): Argument #1 ($params) must be a list array