1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

ext/pdo: Refactor PDOStatement::fetchAll()

This also refactors the internal do_fetch() function to stop doing wonky stuff to handle grouping, which is a feature of fetchAll

Handle PDO_FETCH_KEY_PAIR on its own as GROUP and UNIQUE flags can interfere with it
This commit is contained in:
Gina Peter Banyard
2025-01-03 20:16:26 +00:00
parent 4dd9acccd2
commit 19dd79bdee
3 changed files with 170 additions and 160 deletions

View File

@@ -708,14 +708,38 @@ static void do_fetch_opt_finish(pdo_stmt_t *stmt, int free_ctor_agrs) /* {{{ */
}
/* }}} */
static bool pdo_do_key_pair_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset, HashTable *container)
{
if (!do_fetch_common(stmt, ori, offset)) {
return false;
}
if (stmt->column_count != 2) {
/* TODO: Error? */
pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "PDO::FETCH_KEY_PAIR fetch mode requires the result set to contain exactly 2 columns.");
return false;
}
zval key, val;
fetch_value(stmt, &key, 0, NULL);
fetch_value(stmt, &val, 1, NULL);
if (Z_TYPE(key) == IS_LONG) {
zend_hash_index_update(container, Z_LVAL(key), &val);
} else {
convert_to_string(&key);
zend_symtable_update(container, Z_STR(key), &val);
}
zval_ptr_dtor(&key);
return true;
}
/* perform a fetch.
* Stores values into return_value according to HOW. */
static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type how, enum pdo_fetch_orientation ori, zend_long offset, zval *return_all) /* {{{ */
static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type how, enum pdo_fetch_orientation ori, zend_long offset, zval *group_key) /* {{{ */
{
int flags, idx, old_arg_count = 0;
zend_class_entry *ce = NULL, *old_ce = NULL;
zval grp_val, *pgrp, retval, old_ctor_args = {{0}, {0}, {0}};
int colno;
zval retval, old_ctor_args = {{0}, {0}, {0}};
int i = 0;
if (how == PDO_FETCH_USE_DEFAULT) {
@@ -725,23 +749,54 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h
how = how & ~PDO_FETCH_FLAGS;
if (!do_fetch_common(stmt, ori, offset)) {
return 0;
return false;
}
if (how == PDO_FETCH_BOUND) {
RETVAL_TRUE;
return 1;
}
if ((flags & PDO_FETCH_GROUP) && stmt->fetch.column == -1) {
colno = 1;
} else {
colno = stmt->fetch.column;
return true;
}
if (how == PDO_FETCH_LAZY) {
get_lazy_object(stmt, return_value);
return 1;
return true;
}
/* When fetching a column we only do one value fetch, so handle it separately */
if (how == PDO_FETCH_COLUMN) {
int colno = stmt->fetch.column;
if ((flags & PDO_FETCH_GROUP) && stmt->fetch.column == -1) {
colno = 1;
}
if (colno < 0 ) {
zend_value_error("Column index must be greater than or equal to 0");
return false;
}
if (colno >= stmt->column_count) {
zend_value_error("Invalid column index");
return false;
}
if (flags == PDO_FETCH_GROUP && stmt->fetch.column == -1) {
fetch_value(stmt, return_value, 1, NULL);
} else if (flags == PDO_FETCH_GROUP && colno) {
fetch_value(stmt, return_value, 0, NULL);
} else {
fetch_value(stmt, return_value, colno, NULL);
}
if (group_key) {
if (flags == PDO_FETCH_GROUP && stmt->fetch.column > 0) {
fetch_value(stmt, group_key, colno, NULL);
} else {
fetch_value(stmt, group_key, 0, NULL);
}
convert_to_string(group_key);
}
return true;
}
RETVAL_FALSE;
@@ -752,47 +807,13 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h
case PDO_FETCH_BOTH:
case PDO_FETCH_NUM:
case PDO_FETCH_NAMED:
if (!return_all) {
if (!group_key) {
array_init_size(return_value, stmt->column_count);
} else {
array_init(return_value);
}
break;
case PDO_FETCH_KEY_PAIR:
if (stmt->column_count != 2) {
/* TODO: Error? */
pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "PDO::FETCH_KEY_PAIR fetch mode requires the result set to contain exactly 2 columns.");
return 0;
}
if (!return_all) {
array_init(return_value);
}
break;
case PDO_FETCH_COLUMN:
if (colno < 0 ) {
zend_value_error("Column index must be greater than or equal to 0");
return false;
}
if (colno >= stmt->column_count) {
zend_value_error("Invalid column index");
return false;
}
if (flags == PDO_FETCH_GROUP && stmt->fetch.column == -1) {
fetch_value(stmt, return_value, 1, NULL);
} else if (flags == PDO_FETCH_GROUP && colno) {
fetch_value(stmt, return_value, 0, NULL);
} else {
fetch_value(stmt, return_value, colno, NULL);
}
if (!return_all) {
return 1;
}
break;
case PDO_FETCH_OBJ:
object_init_ex(return_value, ZEND_STANDARD_CLASS_DEF_PTR);
break;
@@ -889,18 +910,10 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h
EMPTY_SWITCH_DEFAULT_CASE();
}
if (return_all && how != PDO_FETCH_KEY_PAIR) {
if (flags == PDO_FETCH_GROUP && how == PDO_FETCH_COLUMN && stmt->fetch.column > 0) {
fetch_value(stmt, &grp_val, colno, NULL);
} else {
fetch_value(stmt, &grp_val, i, NULL);
}
convert_to_string(&grp_val);
if (how == PDO_FETCH_COLUMN) {
i = stmt->column_count; /* no more data to fetch */
} else {
i++;
}
if (group_key) {
fetch_value(stmt, group_key, i, NULL);
convert_to_string(group_key);
i++;
}
for (idx = 0; i < stmt->column_count; i++, idx++) {
@@ -912,22 +925,6 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h
zend_symtable_update(Z_ARRVAL_P(return_value), stmt->columns[i].name, &val);
break;
case PDO_FETCH_KEY_PAIR:
{
zval tmp;
fetch_value(stmt, &tmp, ++i, NULL);
if (Z_TYPE(val) == IS_LONG) {
zend_hash_index_update((return_all ? Z_ARRVAL_P(return_all) : Z_ARRVAL_P(return_value)), Z_LVAL(val), &tmp);
} else {
convert_to_string(&val);
zend_symtable_update((return_all ? Z_ARRVAL_P(return_all) : Z_ARRVAL_P(return_value)), Z_STR(val), &tmp);
}
zval_ptr_dtor(&val);
return 1;
}
break;
case PDO_FETCH_USE_DEFAULT:
case PDO_FETCH_BOTH:
zend_symtable_update(Z_ARRVAL_P(return_value), stmt->columns[i].name, &val);
@@ -1048,10 +1045,7 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h
pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "could not call user-supplied function");
return 0;
} else {
if (return_all) {
zval_ptr_dtor(return_value); /* we don't need that */
ZVAL_COPY_VALUE(return_value, &retval);
} else if (!Z_ISUNDEF(retval)) {
if (!Z_ISUNDEF(retval)) {
ZVAL_COPY_VALUE(return_value, &retval);
}
}
@@ -1064,26 +1058,19 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h
break;
}
if (return_all) {
if ((flags & PDO_FETCH_UNIQUE) == PDO_FETCH_UNIQUE) {
zend_symtable_update(Z_ARRVAL_P(return_all), Z_STR(grp_val), return_value);
} else {
zval grp;
if ((pgrp = zend_symtable_find(Z_ARRVAL_P(return_all), Z_STR(grp_val))) == NULL) {
array_init(&grp);
zend_symtable_update(Z_ARRVAL_P(return_all), Z_STR(grp_val), &grp);
} else {
ZVAL_COPY_VALUE(&grp, pgrp);
}
zend_hash_next_index_insert(Z_ARRVAL(grp), return_value);
}
zval_ptr_dtor_str(&grp_val);
}
return 1;
}
/* }}} */
// TODO Error on the following cases:
// Using any fetch flag with PDO_FETCH_KEY_PAIR
// Combining PDO_FETCH_UNIQUE and PDO_FETCH_GROUP
// Using PDO_FETCH_UNIQUE or PDO_FETCH_GROUP outside of fetchAll()
// Combining PDO_FETCH_PROPS_LATE with a fetch mode different than PDO_FETCH_CLASS
// Improve error detection when combining PDO_FETCH_USE_DEFAULT with
// Reject PDO_FETCH_INTO mode with fetch_all as no support
// Reject $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, value); bypass
static bool pdo_stmt_verify_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_arg_num, bool fetch_all) /* {{{ */
{
int flags = mode & PDO_FETCH_FLAGS;
@@ -1155,6 +1142,17 @@ PHP_METHOD(PDOStatement, fetch)
RETURN_THROWS();
}
int fetch_mode = how & ~PDO_FETCH_FLAGS;
if (fetch_mode == PDO_FETCH_KEY_PAIR) {
array_init_size(return_value, 1);
bool success = pdo_do_key_pair_fetch(stmt, ori, off, Z_ARRVAL_P(return_value));
if (!success) {
zval_dtor(return_value);
PDO_HANDLE_STMT_ERR();
RETURN_FALSE;
}
return;
}
if (!do_fetch(stmt, return_value, how, ori, off, NULL)) {
PDO_HANDLE_STMT_ERR();
RETURN_FALSE;
@@ -1234,12 +1232,10 @@ PHP_METHOD(PDOStatement, fetchColumn)
PHP_METHOD(PDOStatement, fetchAll)
{
zend_long how = PDO_FETCH_USE_DEFAULT;
zval data, *return_all = NULL;
zval *arg2 = NULL;
zend_class_entry *old_ce;
zval old_ctor_args, *ctor_args = NULL;
bool error = false;
int flags, old_arg_count;
uint32_t old_arg_count;
ZEND_PARSE_PARAMETERS_START(0, 3)
Z_PARAM_OPTIONAL
@@ -1253,6 +1249,9 @@ PHP_METHOD(PDOStatement, fetchAll)
RETURN_THROWS();
}
int fetch_mode = how & ~PDO_FETCH_FLAGS;
int flags = how & PDO_FETCH_FLAGS;
old_ce = stmt->fetch.cls.ce;
ZVAL_COPY_VALUE(&old_ctor_args, &stmt->fetch.cls.ctor_args);
old_arg_count = stmt->fetch.cls.fci.param_count;
@@ -1261,7 +1260,7 @@ PHP_METHOD(PDOStatement, fetchAll)
/* TODO Would be good to reuse part of pdo_stmt_setup_fetch_mode() in some way */
switch (how & ~PDO_FETCH_FLAGS) {
switch (fetch_mode) {
case PDO_FETCH_CLASS:
/* Figure out correct class */
if (arg2) {
@@ -1328,7 +1327,7 @@ PHP_METHOD(PDOStatement, fetchAll)
}
stmt->fetch.column = Z_LVAL_P(arg2);
} else {
stmt->fetch.column = how & PDO_FETCH_GROUP ? -1 : 0;
stmt->fetch.column = flags & PDO_FETCH_GROUP ? -1 : 0;
}
break;
@@ -1343,34 +1342,47 @@ PHP_METHOD(PDOStatement, fetchAll)
}
}
flags = how & PDO_FETCH_FLAGS;
if ((how & ~PDO_FETCH_FLAGS) == PDO_FETCH_USE_DEFAULT) {
if (fetch_mode == PDO_FETCH_USE_DEFAULT) {
flags |= stmt->default_fetch_type & PDO_FETCH_FLAGS;
how |= stmt->default_fetch_type & ~PDO_FETCH_FLAGS;
fetch_mode = stmt->default_fetch_type & ~PDO_FETCH_FLAGS;
how = fetch_mode | flags;
}
PDO_STMT_CLEAR_ERR();
if ((how & PDO_FETCH_GROUP) || how == PDO_FETCH_KEY_PAIR ||
(how == PDO_FETCH_USE_DEFAULT && stmt->default_fetch_type == PDO_FETCH_KEY_PAIR)
) {
array_init(return_value);
return_all = return_value;
}
if (!do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, /* offset */ 0, return_all)) {
error = true;
zval data, group_key;
array_init(return_value);
if (fetch_mode == PDO_FETCH_KEY_PAIR) {
while (pdo_do_key_pair_fetch(stmt, PDO_FETCH_ORI_NEXT, /* offset */ 0, Z_ARRVAL_P(return_value)));
PDO_HANDLE_STMT_ERR();
return;
}
if (!error) {
if ((how & PDO_FETCH_GROUP) || how == PDO_FETCH_KEY_PAIR ||
(how == PDO_FETCH_USE_DEFAULT && stmt->default_fetch_type == PDO_FETCH_KEY_PAIR)
) {
while (do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, /* offset */ 0, return_all));
} else {
array_init(return_value);
do {
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &data);
} while (do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, /* offset */ 0, NULL));
// Need to handle the "broken" PDO_FETCH_GROUP|PDO_FETCH_UNIQUE fetch case
//if (flags == PDO_FETCH_GROUP || flags == PDO_FETCH_UNIQUE) {
if (flags & PDO_FETCH_GROUP || flags & PDO_FETCH_UNIQUE) {
while (do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, /* offset */ 0, &group_key)) {
ZEND_ASSERT(Z_TYPE(group_key) == IS_STRING);
if ((flags & PDO_FETCH_UNIQUE) == PDO_FETCH_UNIQUE) {
zend_symtable_update(Z_ARRVAL_P(return_value), Z_STR(group_key), &data);
} else {
zval *group_ptr = zend_symtable_find(Z_ARRVAL_P(return_value), Z_STR(group_key));
zval group;
if (group_ptr == NULL) {
group_ptr = &group;
array_init(group_ptr);
zend_symtable_update(Z_ARRVAL_P(return_value), Z_STR(group_key), group_ptr);
}
zend_hash_next_index_insert(Z_ARRVAL_P(group_ptr), &data);
}
zval_ptr_dtor_str(&group_key);
}
} else {
while (do_fetch(stmt, &data, how, PDO_FETCH_ORI_NEXT, /* offset */ 0, NULL)) {
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &data);
}
}
@@ -1381,13 +1393,7 @@ PHP_METHOD(PDOStatement, fetchAll)
ZVAL_COPY_VALUE(&stmt->fetch.cls.ctor_args, &old_ctor_args);
stmt->fetch.cls.fci.param_count = old_arg_count;
/* on no results, return an empty array */
if (error) {
PDO_HANDLE_STMT_ERR();
if (Z_TYPE_P(return_value) != IS_ARRAY) {
array_init(return_value);
}
}
PDO_HANDLE_STMT_ERR();
}
/* }}} */

View File

@@ -80,11 +80,11 @@ enum pdo_fetch_type {
PDO_FETCH__MAX /* must be last */
};
#define PDO_FETCH_FLAGS 0xFFFF0000 /* fetchAll() modes or'd to PDO_FETCH_XYZ */
#define PDO_FETCH_GROUP 0x00010000 /* fetch into groups */
#define PDO_FETCH_UNIQUE 0x00030000 /* fetch into groups assuming first col is unique */
#define PDO_FETCH_CLASSTYPE 0x00040000 /* fetch class gets its class name from 1st column */
#define PDO_FETCH_SERIALIZE 0x00080000 /* fetch class instances by calling serialize */
#define PDO_FETCH_FLAGS 0xFFFF0000 /* fetchAll() modes or'd to PDO_FETCH_XYZ */
#define PDO_FETCH_GROUP 0x00010000 /* fetch into groups */
#define PDO_FETCH_UNIQUE 0x00030000 /* fetch into groups assuming first col is unique */
#define PDO_FETCH_CLASSTYPE 0x00040000 /* fetch class gets its class name from 1st column */
#define PDO_FETCH_SERIALIZE 0x00080000 /* fetch class instances by calling serialize */
#define PDO_FETCH_PROPS_LATE 0x00100000 /* fetch props after calling ctor */
/* fetch orientation for scrollable cursors */

View File

@@ -72,33 +72,37 @@ object(myclass2)#%d (2) {
myclass::__construct()
myclass::__construct()
myclass::__construct()
array(4) {
[0]=>
object(myclass)#%d (2) {
["val"]=>
string(1) "0"
["group_name"]=>
string(5) "alpha"
array(2) {
["alpha"]=>
array(2) {
[0]=>
object(myclass)#%d (1) {
["val"]=>
string(1) "0"
["group_name"]=>
uninitialized(string)
}
[1]=>
object(myclass)#%d (1) {
["val"]=>
string(1) "1"
["group_name"]=>
uninitialized(string)
}
}
[1]=>
object(stdClass)#%d (2) {
["group_name"]=>
string(4) "beta"
["val"]=>
string(1) "0"
}
[2]=>
object(myclass)#%d (2) {
["val"]=>
string(1) "1"
["group_name"]=>
string(5) "alpha"
}
[3]=>
object(myclass2)#%d (2) {
["val"]=>
string(1) "2"
["group_name"]=>
string(4) "beta"
["beta"]=>
array(2) {
[0]=>
object(stdClass)#%d (1) {
["val"]=>
string(1) "0"
}
[1]=>
object(myclass2)#%d (1) {
["val"]=>
string(1) "2"
["group_name"]=>
uninitialized(string)
}
}
}