diff --git a/UPGRADING b/UPGRADING index b9d8aa79c6293..75a155c3e6ff9 100644 --- a/UPGRADING +++ b/UPGRADING @@ -226,6 +226,9 @@ PHP 8.1 UPGRADE NOTES . Binding in execute has been added to mysqli prepared statements. Parameters can now be passed to mysqli_stmt::execute as an array. RFC: https://wiki.php.net/rfc/mysqli_bind_in_execute + . A new function has been added to mysqli_result called mysqli_fetch_column(). + It allows for fetching single scalar values from the result set. + RFC: https://wiki.php.net/rfc/mysqli_fetch_column - PDO MySQL: . The PDO::MYSQL_ATTR_LOCAL_INFILE_DIRECTORY attribute has been added, which diff --git a/ext/mysqli/mysqli.stub.php b/ext/mysqli/mysqli.stub.php index 99ce77dc4e640..b620d0f2c7d30 100644 --- a/ext/mysqli/mysqli.stub.php +++ b/ext/mysqli/mysqli.stub.php @@ -426,6 +426,11 @@ public function fetch_object(string $class = "stdClass", array $constructor_args */ public function fetch_row() {} + /** + * @alias mysqli_fetch_column + */ + public function fetch_column(int $column = 0): null|int|float|string|false {} + /** * @return bool * @alias mysqli_field_seek @@ -664,6 +669,8 @@ function mysqli_fetch_object(mysqli_result $result, string $class = "stdClass", function mysqli_fetch_row(mysqli_result $result): array|null|false {} +function mysqli_fetch_column(mysqli_result $result, int $column = 0): null|int|float|string|false {} + function mysqli_field_count(mysqli $mysql): int {} function mysqli_field_seek(mysqli_result $result, int $index): bool {} diff --git a/ext/mysqli/mysqli_arginfo.h b/ext/mysqli/mysqli_arginfo.h index fe4504825270d..f8b99b2e5355d 100644 --- a/ext/mysqli/mysqli_arginfo.h +++ b/ext/mysqli/mysqli_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: b0232d18f570208d673ad7535ca60997e038acb8 */ + * Stub hash: 42fdb807a547cfa3ba35bb2b8a4ee0f0d556a18c */ 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) @@ -115,6 +115,11 @@ ZEND_END_ARG_INFO() #define arginfo_mysqli_fetch_row arginfo_mysqli_fetch_assoc +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_mysqli_fetch_column, 0, 1, MAY_BE_NULL|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_OBJ_INFO(0, result, mysqli_result, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, column, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + #define arginfo_mysqli_field_count arginfo_mysqli_errno ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_mysqli_field_seek, 0, 2, _IS_BOOL, 0) @@ -609,6 +614,10 @@ ZEND_END_ARG_INFO() #define arginfo_class_mysqli_result_fetch_row arginfo_class_mysqli_character_set_name +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_mysqli_result_fetch_column, 0, 0, MAY_BE_NULL|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_FALSE) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, column, IS_LONG, 0, "0") +ZEND_END_ARG_INFO() + #define arginfo_class_mysqli_result_field_seek arginfo_class_mysqli_result_fetch_field_direct #define arginfo_class_mysqli_result_free_result arginfo_class_mysqli_character_set_name @@ -709,6 +718,7 @@ ZEND_FUNCTION(mysqli_fetch_array); ZEND_FUNCTION(mysqli_fetch_assoc); ZEND_FUNCTION(mysqli_fetch_object); ZEND_FUNCTION(mysqli_fetch_row); +ZEND_FUNCTION(mysqli_fetch_column); ZEND_FUNCTION(mysqli_field_count); ZEND_FUNCTION(mysqli_field_seek); ZEND_FUNCTION(mysqli_field_tell); @@ -833,6 +843,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(mysqli_fetch_assoc, arginfo_mysqli_fetch_assoc) ZEND_FE(mysqli_fetch_object, arginfo_mysqli_fetch_object) ZEND_FE(mysqli_fetch_row, arginfo_mysqli_fetch_row) + ZEND_FE(mysqli_fetch_column, arginfo_mysqli_fetch_column) ZEND_FE(mysqli_field_count, arginfo_mysqli_field_count) ZEND_FE(mysqli_field_seek, arginfo_mysqli_field_seek) ZEND_FE(mysqli_field_tell, arginfo_mysqli_field_tell) @@ -998,6 +1009,7 @@ static const zend_function_entry class_mysqli_result_methods[] = { ZEND_ME_MAPPING(fetch_assoc, mysqli_fetch_assoc, arginfo_class_mysqli_result_fetch_assoc, ZEND_ACC_PUBLIC) ZEND_ME_MAPPING(fetch_object, mysqli_fetch_object, arginfo_class_mysqli_result_fetch_object, ZEND_ACC_PUBLIC) ZEND_ME_MAPPING(fetch_row, mysqli_fetch_row, arginfo_class_mysqli_result_fetch_row, ZEND_ACC_PUBLIC) + ZEND_ME_MAPPING(fetch_column, mysqli_fetch_column, arginfo_class_mysqli_result_fetch_column, ZEND_ACC_PUBLIC) ZEND_ME_MAPPING(field_seek, mysqli_field_seek, arginfo_class_mysqli_result_field_seek, ZEND_ACC_PUBLIC) ZEND_ME_MAPPING(free_result, mysqli_free_result, arginfo_class_mysqli_result_free_result, ZEND_ACC_PUBLIC) ZEND_ME(mysqli_result, getIterator, arginfo_class_mysqli_result_getIterator, ZEND_ACC_PUBLIC) diff --git a/ext/mysqli/mysqli_nonapi.c b/ext/mysqli/mysqli_nonapi.c index 10de4eee11a5f..9c508ebbf3a39 100644 --- a/ext/mysqli/mysqli_nonapi.c +++ b/ext/mysqli/mysqli_nonapi.c @@ -436,6 +436,39 @@ PHP_FUNCTION(mysqli_fetch_assoc) } /* }}} */ +/* {{{ Fetch a column from the result row */ +PHP_FUNCTION(mysqli_fetch_column) +{ + MYSQL_RES *result; + zval *mysql_result; + zval row_array; + zend_long col_no = 0; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "O|l", &mysql_result, mysqli_result_class_entry, &col_no) == FAILURE) { + RETURN_THROWS(); + } + MYSQLI_FETCH_RESOURCE(result, MYSQL_RES*, mysql_result, "mysqli_result", MYSQLI_STATUS_VALID); + + if (col_no < 0) { + zend_argument_value_error(ERROR_ARG_POS(2), "must be greater than or equal to 0"); + RETURN_THROWS(); + } + if (col_no >= mysql_num_fields(result)) { + zend_argument_value_error(ERROR_ARG_POS(2), "must be less than the number of fields for this result set"); + RETURN_THROWS(); + } + + php_mysqli_fetch_into_hash_aux(&row_array, result, MYSQLI_NUM); + if (Z_TYPE(row_array) != IS_ARRAY) { + zval_ptr_dtor_nogc(&row_array); + RETURN_FALSE; + } + + ZVAL_COPY(return_value, zend_hash_index_find(Z_ARR(row_array), col_no)); + zval_ptr_dtor_nogc(&row_array); +} +/* }}} */ + /* {{{ Fetches all result rows as an associative array, a numeric array, or both */ PHP_FUNCTION(mysqli_fetch_all) { diff --git a/ext/mysqli/tests/mysqli_class_mysqli_result_interface.phpt b/ext/mysqli/tests/mysqli_class_mysqli_result_interface.phpt index 27656417e517f..b3c73f1386b60 100644 --- a/ext/mysqli/tests/mysqli_class_mysqli_result_interface.phpt +++ b/ext/mysqli/tests/mysqli_class_mysqli_result_interface.phpt @@ -35,6 +35,7 @@ require_once('skipifconnectfailure.inc'); 'fetch_fields' => true, 'fetch_object' => true, 'fetch_row' => true, + 'fetch_column' => true, 'field_seek' => true, 'free' => true, 'free_result' => true, diff --git a/ext/mysqli/tests/mysqli_fetch_column.phpt b/ext/mysqli/tests/mysqli_fetch_column.phpt new file mode 100644 index 0000000000000..25c6066a7b5f3 --- /dev/null +++ b/ext/mysqli/tests/mysqli_fetch_column.phpt @@ -0,0 +1,143 @@ +--TEST-- +mysqli_fetch_column() +--SKIPIF-- + +--FILE-- +options(MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true); + +$res = mysqli_query($link, "SELECT + 1 AS a, + 2 AS a +"); +print "[003]\n"; +var_dump(mysqli_fetch_column($res, 0)); + +$res = mysqli_query($link, "SELECT + 1 AS a, + 2 AS a +"); +print "[004]\n"; +var_dump(mysqli_fetch_column($res, 1)); + +$res = mysqli_query($link, "SELECT + 1 AS a, + 2 AS a, + 3 +"); +print "[005]\n"; +var_dump(mysqli_fetch_column($res, 2)); + +$res = mysqli_query($link, "SELECT + 1 AS a, + 2 AS a, + 3, + NULL AS d +"); +print "[006]\n"; +var_dump(mysqli_fetch_column($res, 3)); + +$res = mysqli_query($link, "SELECT + 1 AS a, + 2 AS a, + 3, + NULL AS d, + true AS e +"); +print "[007]\n"; +var_dump(mysqli_fetch_column($res, 4)); + +$res = mysqli_query($link, "SELECT + 1.42 AS a +"); +print "[008]\n"; +var_dump(mysqli_fetch_column($res, 0)); + +$res = mysqli_query($link, "SELECT + 1.42E0 AS a +"); +print "[009]\n"; +var_dump(mysqli_fetch_column($res, 0)); + +$res = mysqli_query($link, "SELECT id, label FROM test ORDER BY id LIMIT 1"); +print "[010]\n"; +try { + var_dump(mysqli_fetch_column($res, -1)); +} catch (\ValueError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +$res = mysqli_query($link, "SELECT id, label FROM test ORDER BY id LIMIT 1"); +print "[011]\n"; +try { + var_dump(mysqli_fetch_column($res, 2)); +} catch (\ValueError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +mysqli_free_result($res); +try { + mysqli_fetch_column($res); +} catch (Error $exception) { + echo $exception->getMessage() . "\n"; +} + +$res = $link->query("SELECT id, label FROM test ORDER BY id LIMIT 2"); + +print "[012]\n"; +var_dump($res->fetch_column()); + +print "[013]\n"; +var_dump($res->fetch_column(1)); + +mysqli_close($link); +?> +--CLEAN-- + +--EXPECT-- +[001] +string(1) "1" +[002] +bool(false) +[003] +int(1) +[004] +int(2) +[005] +int(3) +[006] +NULL +[007] +int(1) +[008] +string(4) "1.42" +[009] +float(1.42) +[010] +mysqli_fetch_column(): Argument #2 ($column) must be greater than or equal to 0 +[011] +mysqli_fetch_column(): Argument #2 ($column) must be less than the number of fields for this result set +mysqli_result object is already closed +[012] +int(1) +[013] +string(1) "b"