Skip to content

Commit 19dd79b

Browse files
committed
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
1 parent 4dd9acc commit 19dd79b

File tree

3 files changed

+170
-160
lines changed

3 files changed

+170
-160
lines changed

ext/pdo/pdo_stmt.c

Lines changed: 134 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -708,14 +708,38 @@ static void do_fetch_opt_finish(pdo_stmt_t *stmt, int free_ctor_agrs) /* {{{ */
708708
}
709709
/* }}} */
710710

711+
static bool pdo_do_key_pair_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset, HashTable *container)
712+
{
713+
if (!do_fetch_common(stmt, ori, offset)) {
714+
return false;
715+
}
716+
if (stmt->column_count != 2) {
717+
/* TODO: Error? */
718+
pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "PDO::FETCH_KEY_PAIR fetch mode requires the result set to contain exactly 2 columns.");
719+
return false;
720+
}
721+
722+
zval key, val;
723+
fetch_value(stmt, &key, 0, NULL);
724+
fetch_value(stmt, &val, 1, NULL);
725+
726+
if (Z_TYPE(key) == IS_LONG) {
727+
zend_hash_index_update(container, Z_LVAL(key), &val);
728+
} else {
729+
convert_to_string(&key);
730+
zend_symtable_update(container, Z_STR(key), &val);
731+
}
732+
zval_ptr_dtor(&key);
733+
return true;
734+
}
735+
711736
/* perform a fetch.
712737
* Stores values into return_value according to HOW. */
713-
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) /* {{{ */
738+
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) /* {{{ */
714739
{
715740
int flags, idx, old_arg_count = 0;
716741
zend_class_entry *ce = NULL, *old_ce = NULL;
717-
zval grp_val, *pgrp, retval, old_ctor_args = {{0}, {0}, {0}};
718-
int colno;
742+
zval retval, old_ctor_args = {{0}, {0}, {0}};
719743
int i = 0;
720744

721745
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
725749
how = how & ~PDO_FETCH_FLAGS;
726750

727751
if (!do_fetch_common(stmt, ori, offset)) {
728-
return 0;
752+
return false;
729753
}
730754

731755
if (how == PDO_FETCH_BOUND) {
732756
RETVAL_TRUE;
733-
return 1;
734-
}
735-
736-
if ((flags & PDO_FETCH_GROUP) && stmt->fetch.column == -1) {
737-
colno = 1;
738-
} else {
739-
colno = stmt->fetch.column;
757+
return true;
740758
}
741759

742760
if (how == PDO_FETCH_LAZY) {
743761
get_lazy_object(stmt, return_value);
744-
return 1;
762+
return true;
763+
}
764+
765+
/* When fetching a column we only do one value fetch, so handle it separately */
766+
if (how == PDO_FETCH_COLUMN) {
767+
int colno = stmt->fetch.column;
768+
769+
if ((flags & PDO_FETCH_GROUP) && stmt->fetch.column == -1) {
770+
colno = 1;
771+
}
772+
773+
if (colno < 0 ) {
774+
zend_value_error("Column index must be greater than or equal to 0");
775+
return false;
776+
}
777+
778+
if (colno >= stmt->column_count) {
779+
zend_value_error("Invalid column index");
780+
return false;
781+
}
782+
783+
if (flags == PDO_FETCH_GROUP && stmt->fetch.column == -1) {
784+
fetch_value(stmt, return_value, 1, NULL);
785+
} else if (flags == PDO_FETCH_GROUP && colno) {
786+
fetch_value(stmt, return_value, 0, NULL);
787+
} else {
788+
fetch_value(stmt, return_value, colno, NULL);
789+
}
790+
791+
if (group_key) {
792+
if (flags == PDO_FETCH_GROUP && stmt->fetch.column > 0) {
793+
fetch_value(stmt, group_key, colno, NULL);
794+
} else {
795+
fetch_value(stmt, group_key, 0, NULL);
796+
}
797+
convert_to_string(group_key);
798+
}
799+
return true;
745800
}
746801

747802
RETVAL_FALSE;
@@ -752,47 +807,13 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h
752807
case PDO_FETCH_BOTH:
753808
case PDO_FETCH_NUM:
754809
case PDO_FETCH_NAMED:
755-
if (!return_all) {
810+
if (!group_key) {
756811
array_init_size(return_value, stmt->column_count);
757812
} else {
758813
array_init(return_value);
759814
}
760815
break;
761816

762-
case PDO_FETCH_KEY_PAIR:
763-
if (stmt->column_count != 2) {
764-
/* TODO: Error? */
765-
pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "PDO::FETCH_KEY_PAIR fetch mode requires the result set to contain exactly 2 columns.");
766-
return 0;
767-
}
768-
if (!return_all) {
769-
array_init(return_value);
770-
}
771-
break;
772-
773-
case PDO_FETCH_COLUMN:
774-
if (colno < 0 ) {
775-
zend_value_error("Column index must be greater than or equal to 0");
776-
return false;
777-
}
778-
779-
if (colno >= stmt->column_count) {
780-
zend_value_error("Invalid column index");
781-
return false;
782-
}
783-
784-
if (flags == PDO_FETCH_GROUP && stmt->fetch.column == -1) {
785-
fetch_value(stmt, return_value, 1, NULL);
786-
} else if (flags == PDO_FETCH_GROUP && colno) {
787-
fetch_value(stmt, return_value, 0, NULL);
788-
} else {
789-
fetch_value(stmt, return_value, colno, NULL);
790-
}
791-
if (!return_all) {
792-
return 1;
793-
}
794-
break;
795-
796817
case PDO_FETCH_OBJ:
797818
object_init_ex(return_value, ZEND_STANDARD_CLASS_DEF_PTR);
798819
break;
@@ -889,18 +910,10 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h
889910
EMPTY_SWITCH_DEFAULT_CASE();
890911
}
891912

892-
if (return_all && how != PDO_FETCH_KEY_PAIR) {
893-
if (flags == PDO_FETCH_GROUP && how == PDO_FETCH_COLUMN && stmt->fetch.column > 0) {
894-
fetch_value(stmt, &grp_val, colno, NULL);
895-
} else {
896-
fetch_value(stmt, &grp_val, i, NULL);
897-
}
898-
convert_to_string(&grp_val);
899-
if (how == PDO_FETCH_COLUMN) {
900-
i = stmt->column_count; /* no more data to fetch */
901-
} else {
902-
i++;
903-
}
913+
if (group_key) {
914+
fetch_value(stmt, group_key, i, NULL);
915+
convert_to_string(group_key);
916+
i++;
904917
}
905918

906919
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
912925
zend_symtable_update(Z_ARRVAL_P(return_value), stmt->columns[i].name, &val);
913926
break;
914927

915-
case PDO_FETCH_KEY_PAIR:
916-
{
917-
zval tmp;
918-
fetch_value(stmt, &tmp, ++i, NULL);
919-
920-
if (Z_TYPE(val) == IS_LONG) {
921-
zend_hash_index_update((return_all ? Z_ARRVAL_P(return_all) : Z_ARRVAL_P(return_value)), Z_LVAL(val), &tmp);
922-
} else {
923-
convert_to_string(&val);
924-
zend_symtable_update((return_all ? Z_ARRVAL_P(return_all) : Z_ARRVAL_P(return_value)), Z_STR(val), &tmp);
925-
}
926-
zval_ptr_dtor(&val);
927-
return 1;
928-
}
929-
break;
930-
931928
case PDO_FETCH_USE_DEFAULT:
932929
case PDO_FETCH_BOTH:
933930
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
10481045
pdo_raise_impl_error(stmt->dbh, stmt, "HY000", "could not call user-supplied function");
10491046
return 0;
10501047
} else {
1051-
if (return_all) {
1052-
zval_ptr_dtor(return_value); /* we don't need that */
1053-
ZVAL_COPY_VALUE(return_value, &retval);
1054-
} else if (!Z_ISUNDEF(retval)) {
1048+
if (!Z_ISUNDEF(retval)) {
10551049
ZVAL_COPY_VALUE(return_value, &retval);
10561050
}
10571051
}
@@ -1064,26 +1058,19 @@ static bool do_fetch(pdo_stmt_t *stmt, zval *return_value, enum pdo_fetch_type h
10641058
break;
10651059
}
10661060

1067-
if (return_all) {
1068-
if ((flags & PDO_FETCH_UNIQUE) == PDO_FETCH_UNIQUE) {
1069-
zend_symtable_update(Z_ARRVAL_P(return_all), Z_STR(grp_val), return_value);
1070-
} else {
1071-
zval grp;
1072-
if ((pgrp = zend_symtable_find(Z_ARRVAL_P(return_all), Z_STR(grp_val))) == NULL) {
1073-
array_init(&grp);
1074-
zend_symtable_update(Z_ARRVAL_P(return_all), Z_STR(grp_val), &grp);
1075-
} else {
1076-
ZVAL_COPY_VALUE(&grp, pgrp);
1077-
}
1078-
zend_hash_next_index_insert(Z_ARRVAL(grp), return_value);
1079-
}
1080-
zval_ptr_dtor_str(&grp_val);
1081-
}
1082-
10831061
return 1;
10841062
}
10851063
/* }}} */
10861064

1065+
1066+
// TODO Error on the following cases:
1067+
// Using any fetch flag with PDO_FETCH_KEY_PAIR
1068+
// Combining PDO_FETCH_UNIQUE and PDO_FETCH_GROUP
1069+
// Using PDO_FETCH_UNIQUE or PDO_FETCH_GROUP outside of fetchAll()
1070+
// Combining PDO_FETCH_PROPS_LATE with a fetch mode different than PDO_FETCH_CLASS
1071+
// Improve error detection when combining PDO_FETCH_USE_DEFAULT with
1072+
// Reject PDO_FETCH_INTO mode with fetch_all as no support
1073+
// Reject $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, value); bypass
10871074
static bool pdo_stmt_verify_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_arg_num, bool fetch_all) /* {{{ */
10881075
{
10891076
int flags = mode & PDO_FETCH_FLAGS;
@@ -1155,6 +1142,17 @@ PHP_METHOD(PDOStatement, fetch)
11551142
RETURN_THROWS();
11561143
}
11571144

1145+
int fetch_mode = how & ~PDO_FETCH_FLAGS;
1146+
if (fetch_mode == PDO_FETCH_KEY_PAIR) {
1147+
array_init_size(return_value, 1);
1148+
bool success = pdo_do_key_pair_fetch(stmt, ori, off, Z_ARRVAL_P(return_value));
1149+
if (!success) {
1150+
zval_dtor(return_value);
1151+
PDO_HANDLE_STMT_ERR();
1152+
RETURN_FALSE;
1153+
}
1154+
return;
1155+
}
11581156
if (!do_fetch(stmt, return_value, how, ori, off, NULL)) {
11591157
PDO_HANDLE_STMT_ERR();
11601158
RETURN_FALSE;
@@ -1234,12 +1232,10 @@ PHP_METHOD(PDOStatement, fetchColumn)
12341232
PHP_METHOD(PDOStatement, fetchAll)
12351233
{
12361234
zend_long how = PDO_FETCH_USE_DEFAULT;
1237-
zval data, *return_all = NULL;
12381235
zval *arg2 = NULL;
12391236
zend_class_entry *old_ce;
12401237
zval old_ctor_args, *ctor_args = NULL;
1241-
bool error = false;
1242-
int flags, old_arg_count;
1238+
uint32_t old_arg_count;
12431239

12441240
ZEND_PARSE_PARAMETERS_START(0, 3)
12451241
Z_PARAM_OPTIONAL
@@ -1253,6 +1249,9 @@ PHP_METHOD(PDOStatement, fetchAll)
12531249
RETURN_THROWS();
12541250
}
12551251

1252+
int fetch_mode = how & ~PDO_FETCH_FLAGS;
1253+
int flags = how & PDO_FETCH_FLAGS;
1254+
12561255
old_ce = stmt->fetch.cls.ce;
12571256
ZVAL_COPY_VALUE(&old_ctor_args, &stmt->fetch.cls.ctor_args);
12581257
old_arg_count = stmt->fetch.cls.fci.param_count;
@@ -1261,7 +1260,7 @@ PHP_METHOD(PDOStatement, fetchAll)
12611260

12621261
/* TODO Would be good to reuse part of pdo_stmt_setup_fetch_mode() in some way */
12631262

1264-
switch (how & ~PDO_FETCH_FLAGS) {
1263+
switch (fetch_mode) {
12651264
case PDO_FETCH_CLASS:
12661265
/* Figure out correct class */
12671266
if (arg2) {
@@ -1328,7 +1327,7 @@ PHP_METHOD(PDOStatement, fetchAll)
13281327
}
13291328
stmt->fetch.column = Z_LVAL_P(arg2);
13301329
} else {
1331-
stmt->fetch.column = how & PDO_FETCH_GROUP ? -1 : 0;
1330+
stmt->fetch.column = flags & PDO_FETCH_GROUP ? -1 : 0;
13321331
}
13331332
break;
13341333

@@ -1343,34 +1342,47 @@ PHP_METHOD(PDOStatement, fetchAll)
13431342
}
13441343
}
13451344

1346-
flags = how & PDO_FETCH_FLAGS;
13471345

1348-
if ((how & ~PDO_FETCH_FLAGS) == PDO_FETCH_USE_DEFAULT) {
1346+
if (fetch_mode == PDO_FETCH_USE_DEFAULT) {
13491347
flags |= stmt->default_fetch_type & PDO_FETCH_FLAGS;
1350-
how |= stmt->default_fetch_type & ~PDO_FETCH_FLAGS;
1348+
fetch_mode = stmt->default_fetch_type & ~PDO_FETCH_FLAGS;
1349+
how = fetch_mode | flags;
13511350
}
13521351

13531352
PDO_STMT_CLEAR_ERR();
1354-
if ((how & PDO_FETCH_GROUP) || how == PDO_FETCH_KEY_PAIR ||
1355-
(how == PDO_FETCH_USE_DEFAULT && stmt->default_fetch_type == PDO_FETCH_KEY_PAIR)
1356-
) {
1357-
array_init(return_value);
1358-
return_all = return_value;
1359-
}
1360-
if (!do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, /* offset */ 0, return_all)) {
1361-
error = true;
1353+
1354+
zval data, group_key;
1355+
1356+
array_init(return_value);
1357+
1358+
if (fetch_mode == PDO_FETCH_KEY_PAIR) {
1359+
while (pdo_do_key_pair_fetch(stmt, PDO_FETCH_ORI_NEXT, /* offset */ 0, Z_ARRVAL_P(return_value)));
1360+
PDO_HANDLE_STMT_ERR();
1361+
return;
13621362
}
13631363

1364-
if (!error) {
1365-
if ((how & PDO_FETCH_GROUP) || how == PDO_FETCH_KEY_PAIR ||
1366-
(how == PDO_FETCH_USE_DEFAULT && stmt->default_fetch_type == PDO_FETCH_KEY_PAIR)
1367-
) {
1368-
while (do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, /* offset */ 0, return_all));
1369-
} else {
1370-
array_init(return_value);
1371-
do {
1372-
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &data);
1373-
} while (do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, /* offset */ 0, NULL));
1364+
// Need to handle the "broken" PDO_FETCH_GROUP|PDO_FETCH_UNIQUE fetch case
1365+
//if (flags == PDO_FETCH_GROUP || flags == PDO_FETCH_UNIQUE) {
1366+
if (flags & PDO_FETCH_GROUP || flags & PDO_FETCH_UNIQUE) {
1367+
while (do_fetch(stmt, &data, how | flags, PDO_FETCH_ORI_NEXT, /* offset */ 0, &group_key)) {
1368+
ZEND_ASSERT(Z_TYPE(group_key) == IS_STRING);
1369+
if ((flags & PDO_FETCH_UNIQUE) == PDO_FETCH_UNIQUE) {
1370+
zend_symtable_update(Z_ARRVAL_P(return_value), Z_STR(group_key), &data);
1371+
} else {
1372+
zval *group_ptr = zend_symtable_find(Z_ARRVAL_P(return_value), Z_STR(group_key));
1373+
zval group;
1374+
if (group_ptr == NULL) {
1375+
group_ptr = &group;
1376+
array_init(group_ptr);
1377+
zend_symtable_update(Z_ARRVAL_P(return_value), Z_STR(group_key), group_ptr);
1378+
}
1379+
zend_hash_next_index_insert(Z_ARRVAL_P(group_ptr), &data);
1380+
}
1381+
zval_ptr_dtor_str(&group_key);
1382+
}
1383+
} else {
1384+
while (do_fetch(stmt, &data, how, PDO_FETCH_ORI_NEXT, /* offset */ 0, NULL)) {
1385+
zend_hash_next_index_insert_new(Z_ARRVAL_P(return_value), &data);
13741386
}
13751387
}
13761388

@@ -1381,13 +1393,7 @@ PHP_METHOD(PDOStatement, fetchAll)
13811393
ZVAL_COPY_VALUE(&stmt->fetch.cls.ctor_args, &old_ctor_args);
13821394
stmt->fetch.cls.fci.param_count = old_arg_count;
13831395

1384-
/* on no results, return an empty array */
1385-
if (error) {
1386-
PDO_HANDLE_STMT_ERR();
1387-
if (Z_TYPE_P(return_value) != IS_ARRAY) {
1388-
array_init(return_value);
1389-
}
1390-
}
1396+
PDO_HANDLE_STMT_ERR();
13911397
}
13921398
/* }}} */
13931399

0 commit comments

Comments
 (0)