ext/sqlite3: Sqlite3Result::fetchAll()

support associative and indexes arrays for results.

close GH-1884
This commit is contained in:
David Carlier 2025-06-19 23:38:51 +01:00
parent 22bd2ae63f
commit 375316d0e2
No known key found for this signature in database
GPG key ID: 8486F847B4B94EF1
6 changed files with 205 additions and 25 deletions

2
NEWS
View file

@ -249,6 +249,8 @@ PHP NEWS
(David Carlier)
. Added Sqlite3Stmt::explain to produce a explain query plan from
the statement. (David Carlier)
. Added Sqlite3Result::fetchAll to returns all results at once from a query.
(David Carlier)
- Standard:
. Fixed crypt() tests on musl when using --with-external-libcrypt

View file

@ -212,6 +212,11 @@ PHP 8.5 UPGRADE NOTES
now have an optional $lang parameter.
This support solves compatibility with .NET SOAP clients.
- Sqlite:
. Added class constants Sqlite3Stmt::EXPLAIN_MODE_PREPARED,
Sqlite3Stmt::EXPLAIN_MODE_EXPLAIN and
Sqlite3Stmt::EXPLAIN_MODE_EXPLAIN_QUERY_PLAN.
- XSL:
. The $namespace argument of XSLTProcessor::getParameter(),
XSLTProcessor::setParameter() and XSLTProcessor::removeParameter()

View file

@ -39,6 +39,7 @@ static PHP_GINIT_FUNCTION(sqlite3);
static int php_sqlite3_authorizer(void *autharg, int action, const char *arg1, const char *arg2, const char *arg3, const char *arg4);
static void sqlite3_param_dtor(zval *data);
static int php_sqlite3_compare_stmt_free(php_sqlite3_stmt **stmt_obj_ptr, sqlite3_stmt *statement);
static zend_always_inline void php_sqlite3_fetch_one(int n_cols, php_sqlite3_result *result_obj, zend_long mode, zval *result);
#define SQLITE3_CHECK_INITIALIZED(db_obj, member, class_name) \
if (!(db_obj) || !(member)) { \
@ -1991,7 +1992,7 @@ PHP_METHOD(SQLite3Result, fetchArray)
{
php_sqlite3_result *result_obj;
zval *object = ZEND_THIS;
int i, ret;
int ret;
zend_long mode = PHP_SQLITE3_BOTH;
result_obj = Z_SQLITE3_RESULT_P(object);
@ -2028,26 +2029,8 @@ PHP_METHOD(SQLite3Result, fetchArray)
array_init(return_value);
for (i = 0; i < n_cols; i++) {
zval data;
php_sqlite3_fetch_one(n_cols, result_obj, mode, return_value);
sqlite_value_to_zval(result_obj->stmt_obj->stmt, i, &data);
if (mode & PHP_SQLITE3_NUM) {
add_index_zval(return_value, i, &data);
}
if (mode & PHP_SQLITE3_ASSOC) {
if (mode & PHP_SQLITE3_NUM) {
if (Z_REFCOUNTED(data)) {
Z_ADDREF(data);
}
}
/* Note: we can't use the "add_new" variant here instead of "update" because
* when the same column name is encountered, the last result should be taken. */
zend_symtable_update(Z_ARR_P(return_value), result_obj->column_names[i], &data);
}
}
break;
case SQLITE_DONE:
@ -2071,6 +2054,61 @@ static void sqlite3result_clear_column_names_cache(php_sqlite3_result *result) {
result->column_count = -1;
}
PHP_METHOD(SQLite3Result, fetchAll)
{
int i, nb_cols;
bool done = false;
php_sqlite3_result *result_obj;
zval *object = ZEND_THIS;
zend_long mode = PHP_SQLITE3_BOTH;
result_obj = Z_SQLITE3_RESULT_P(object);
ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_LONG(mode)
ZEND_PARSE_PARAMETERS_END();
SQLITE3_CHECK_INITIALIZED(result_obj->db_obj, result_obj->stmt_obj->initialised, SQLite3Result)
nb_cols = sqlite3_column_count(result_obj->stmt_obj->stmt);
if (mode & PHP_SQLITE3_ASSOC) {
sqlite3result_clear_column_names_cache(result_obj);
result_obj->column_names = emalloc(nb_cols * sizeof(zend_string*));
for (i = 0; i < nb_cols; i++) {
const char *column = sqlite3_column_name(result_obj->stmt_obj->stmt, i);
result_obj->column_names[i] = zend_string_init(column, strlen(column), 0);
}
}
result_obj->column_count = nb_cols;
array_init(return_value);
while (!done) {
int step = sqlite3_step(result_obj->stmt_obj->stmt);
switch (step) {
case SQLITE_ROW: {
zval result;
array_init_size(&result, result_obj->column_count);
php_sqlite3_fetch_one(result_obj->column_count, result_obj, mode, &result);
add_next_index_zval(return_value, &result);
break;
}
case SQLITE_DONE:
done = true;
break;
default:
if (!EG(exception)) {
php_sqlite3_error(result_obj->db_obj, sqlite3_errcode(sqlite3_db_handle(result_obj->stmt_obj->stmt)), "Unable to execute statement: %s", sqlite3_errmsg(sqlite3_db_handle(result_obj->stmt_obj->stmt)));
}
zval_ptr_dtor(return_value);
RETURN_FALSE;
}
}
}
/* {{{ Resets the result set back to the first row. */
PHP_METHOD(SQLite3Result, reset)
{
@ -2429,6 +2467,29 @@ static void sqlite3_param_dtor(zval *data) /* {{{ */
}
/* }}} */
static zend_always_inline void php_sqlite3_fetch_one(int n_cols, php_sqlite3_result *result_obj, zend_long mode, zval *result)
{
for (int i = 0; i < n_cols; i ++) {
zval data;
sqlite_value_to_zval(result_obj->stmt_obj->stmt, i, &data);
if (mode & PHP_SQLITE3_NUM) {
add_index_zval(result, i, &data);
}
if (mode & PHP_SQLITE3_ASSOC) {
if (mode & PHP_SQLITE3_NUM) {
if (Z_REFCOUNTED(data)) {
Z_ADDREF(data);
}
}
/* Note: we can't use the "add_new" variant here instead of "update" because
* when the same column name is encountered, the last result should be taken. */
zend_symtable_update(Z_ARR_P(result), result_obj->column_names[i], &data);
}
}
}
/* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(sqlite3)
{

View file

@ -302,6 +302,8 @@ class SQLite3Result
/** @tentative-return-type */
public function fetchArray(int $mode = SQLITE3_BOTH): array|false {}
public function fetchAll(int $mode = SQLITE3_BOTH): array|false {}
/** @tentative-return-type */
public function reset(): bool {}

View file

@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: c3216eada9881743cbd3aa1510f1200b7ce0d942 */
* Stub hash: da91c32c6070c808d6e1b01894b5f8beedda7b45 */
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_SQLite3___construct, 0, 0, 1)
ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 0)
@ -173,6 +173,10 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX(arginfo_class_SQLite3Result_fe
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "SQLITE3_BOTH")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_class_SQLite3Result_fetchAll, 0, 0, MAY_BE_ARRAY|MAY_BE_FALSE)
ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, mode, IS_LONG, 0, "SQLITE3_BOTH")
ZEND_END_ARG_INFO()
#define arginfo_class_SQLite3Result_reset arginfo_class_SQLite3_close
#define arginfo_class_SQLite3Result_finalize arginfo_class_SQLite3Stmt_close
@ -224,6 +228,7 @@ ZEND_METHOD(SQLite3Result, numColumns);
ZEND_METHOD(SQLite3Result, columnName);
ZEND_METHOD(SQLite3Result, columnType);
ZEND_METHOD(SQLite3Result, fetchArray);
ZEND_METHOD(SQLite3Result, fetchAll);
ZEND_METHOD(SQLite3Result, reset);
ZEND_METHOD(SQLite3Result, finalize);
@ -284,6 +289,7 @@ static const zend_function_entry class_SQLite3Result_methods[] = {
ZEND_ME(SQLite3Result, columnName, arginfo_class_SQLite3Result_columnName, ZEND_ACC_PUBLIC)
ZEND_ME(SQLite3Result, columnType, arginfo_class_SQLite3Result_columnType, ZEND_ACC_PUBLIC)
ZEND_ME(SQLite3Result, fetchArray, arginfo_class_SQLite3Result_fetchArray, ZEND_ACC_PUBLIC)
ZEND_ME(SQLite3Result, fetchAll, arginfo_class_SQLite3Result_fetchAll, ZEND_ACC_PUBLIC)
ZEND_ME(SQLite3Result, reset, arginfo_class_SQLite3Result_reset, ZEND_ACC_PUBLIC)
ZEND_ME(SQLite3Result, finalize, arginfo_class_SQLite3Result_finalize, ZEND_ACC_PUBLIC)
ZEND_FE_END
@ -564,16 +570,12 @@ static zend_class_entry *register_class_SQLite3Stmt(void)
zend_string *const_EXPLAIN_MODE_PREPARED_name = zend_string_init_interned("EXPLAIN_MODE_PREPARED", sizeof("EXPLAIN_MODE_PREPARED") - 1, 1);
zend_declare_typed_class_constant(class_entry, const_EXPLAIN_MODE_PREPARED_name, &const_EXPLAIN_MODE_PREPARED_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
zend_string_release(const_EXPLAIN_MODE_PREPARED_name);
#endif
#if SQLITE_VERSION_NUMBER >= 3043000
zval const_EXPLAIN_MODE_EXPLAIN_value;
ZVAL_LONG(&const_EXPLAIN_MODE_EXPLAIN_value, 1);
zend_string *const_EXPLAIN_MODE_EXPLAIN_name = zend_string_init_interned("EXPLAIN_MODE_EXPLAIN", sizeof("EXPLAIN_MODE_EXPLAIN") - 1, 1);
zend_declare_typed_class_constant(class_entry, const_EXPLAIN_MODE_EXPLAIN_name, &const_EXPLAIN_MODE_EXPLAIN_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
zend_string_release(const_EXPLAIN_MODE_EXPLAIN_name);
#endif
#if SQLITE_VERSION_NUMBER >= 3043000
zval const_EXPLAIN_MODE_EXPLAIN_QUERY_PLAN_value;
ZVAL_LONG(&const_EXPLAIN_MODE_EXPLAIN_QUERY_PLAN_value, 2);

View file

@ -0,0 +1,108 @@
--TEST--
SQLite3Result::fetchAll usage
--EXTENSIONS--
sqlite3
--FILE--
<?php
$conn = new sqlite3(':memory:');
$conn->query('CREATE TABLE users (id INTEGER NOT NULL, num INTEGER NOT NULL, PRIMARY KEY(id))');
$stmt = $conn->query('insert into users (id, num) values (1, 1)');
$stmt = $conn->query('insert into users (id, num) values (2, 2)');
$stmt = $conn->query('SELECT * FROM users');
$rowall = $stmt->fetchAll();
var_dump($rowall);
$stmt->reset();
$rowfetch = [];
while (($row = $stmt->fetchArray())) $rowfetch[] = $row;
var_dump($rowfetch);
var_dump($rowall == $rowfetch);
$stmt->reset();
var_dump($stmt->fetchAll(SQLITE3_NUM));
$stmt->reset();
var_dump($stmt->fetchAll(SQLITE3_ASSOC));
?>
--EXPECT--
array(2) {
[0]=>
array(4) {
[0]=>
int(1)
["id"]=>
int(1)
[1]=>
int(1)
["num"]=>
int(1)
}
[1]=>
array(4) {
[0]=>
int(2)
["id"]=>
int(2)
[1]=>
int(2)
["num"]=>
int(2)
}
}
array(2) {
[0]=>
array(4) {
[0]=>
int(1)
["id"]=>
int(1)
[1]=>
int(1)
["num"]=>
int(1)
}
[1]=>
array(4) {
[0]=>
int(2)
["id"]=>
int(2)
[1]=>
int(2)
["num"]=>
int(2)
}
}
bool(true)
array(2) {
[0]=>
array(2) {
[0]=>
int(1)
[1]=>
int(1)
}
[1]=>
array(2) {
[0]=>
int(2)
[1]=>
int(2)
}
}
array(2) {
[0]=>
array(2) {
["id"]=>
int(1)
["num"]=>
int(1)
}
[1]=>
array(2) {
["id"]=>
int(2)
["num"]=>
int(2)
}
}