summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDharman <tekiela246@gmail.com>2020-12-02 21:24:20 +0000
committerNikita Popov <nikita.ppv@gmail.com>2020-12-04 16:59:47 +0100
commita83cc03c138b8cf27a840bd7cd913eb7050e55ba (patch)
tree981e1ab2afa561c1ced7454ae551f80b865e51f5
parent8588ae72156eeead928a8fe93bb8a5ab293f1e89 (diff)
downloadphp-git-a83cc03c138b8cf27a840bd7cd913eb7050e55ba.tar.gz
Fixed bug #80458
If there is no result set (e.g. for upsert queries), still allow fetching to occur without error, i.e. treat it the same way as an empty result set. This normalizes behavior between native and emulated prepared statements and addresses a regression in PHP 7.4.13.
-rw-r--r--NEWS4
-rw-r--r--ext/pdo_mysql/mysql_statement.c21
-rw-r--r--ext/pdo_mysql/tests/bug80458.phpt186
3 files changed, 200 insertions, 11 deletions
diff --git a/NEWS b/NEWS
index c7fe41fd88..dab2198b9b 100644
--- a/NEWS
+++ b/NEWS
@@ -28,6 +28,10 @@ PHP NEWS
. Fixed bug #73809 (Phar Zip parse crash - mmap fail). (cmb)
. Fixed #75102 (`PharData` says invalid checksum for valid tar). (cmb)
+- PDO MySQL:
+ . Fixed bug #80458 (PDOStatement::fetchAll() throws for upsert queries).
+ (Kamil Tekiela)
+
- Phpdbg:
. Fixed bug #76813 (Access violation near NULL on source operand). (cmb)
diff --git a/ext/pdo_mysql/mysql_statement.c b/ext/pdo_mysql/mysql_statement.c
index dbac374171..58711459ae 100644
--- a/ext/pdo_mysql/mysql_statement.c
+++ b/ext/pdo_mysql/mysql_statement.c
@@ -621,7 +621,12 @@ static int pdo_mysql_stmt_param_hook(pdo_stmt_t *stmt, struct pdo_bound_param_da
static int pdo_mysql_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori, zend_long offset) /* {{{ */
{
pdo_mysql_stmt *S = (pdo_mysql_stmt*)stmt->driver_data;
-#if PDO_USE_MYSQLND
+
+ if (!S->result) {
+ PDO_DBG_RETURN(0);
+ }
+
+#ifdef PDO_USE_MYSQLND
zend_bool fetched_anything;
PDO_DBG_ENTER("pdo_mysql_stmt_fetch");
@@ -634,6 +639,10 @@ static int pdo_mysql_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori
PDO_DBG_RETURN(1);
}
+
+ if (!S->stmt && S->current_data) {
+ mnd_free(S->current_data);
+ }
#else
int ret;
@@ -657,16 +666,6 @@ static int pdo_mysql_stmt_fetch(pdo_stmt_t *stmt, enum pdo_fetch_orientation ori
}
#endif /* PDO_USE_MYSQLND */
- if (!S->result) {
- strcpy(stmt->error_code, "HY000");
- PDO_DBG_RETURN(0);
- }
-#if PDO_USE_MYSQLND
- if (!S->stmt && S->current_data) {
- mnd_free(S->current_data);
- }
-#endif /* PDO_USE_MYSQLND */
-
if ((S->current_data = mysql_fetch_row(S->result)) == NULL) {
if (!S->H->buffered && mysql_errno(S->H->server)) {
pdo_mysql_error_stmt(stmt);
diff --git a/ext/pdo_mysql/tests/bug80458.phpt b/ext/pdo_mysql/tests/bug80458.phpt
new file mode 100644
index 0000000000..86e171d302
--- /dev/null
+++ b/ext/pdo_mysql/tests/bug80458.phpt
@@ -0,0 +1,186 @@
+--TEST--
+Bug #80458 PDOStatement::fetchAll() throws for upsert queries
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
+require_once(__DIR__ . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+require_once(__DIR__ . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+
+$db = MySQLPDOTest::factory();
+$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+
+$db->query('DROP TABLE IF EXISTS test');
+$db->query('CREATE TABLE test (first int) ENGINE = InnoDB');
+$res = $db->query('INSERT INTO test(first) VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12),(13),(14),(15),(16)');
+var_dump($res->fetchAll());
+
+$stmt = $db->prepare('DELETE FROM test WHERE first=1');
+$stmt->execute();
+var_dump($stmt->fetchAll());
+
+$res = $db->query('DELETE FROM test WHERE first=2');
+var_dump($res->fetchAll());
+
+$stmt2 = $db->prepare('DELETE FROM test WHERE first=3');
+$stmt2->execute();
+foreach($stmt2 as $row){
+ // expect nothing
+}
+
+$stmt3 = $db->prepare('DELETE FROM test WHERE first=4');
+$stmt3->execute();
+var_dump($stmt3->fetch(PDO::FETCH_ASSOC));
+
+$stmt = $db->prepare('SELECT first FROM test WHERE first=5');
+$stmt->execute();
+var_dump($stmt->fetchAll());
+
+$db->exec('DROP PROCEDURE IF EXISTS nores');
+$db->exec('CREATE PROCEDURE nores() BEGIN DELETE FROM test WHERE first=6; END;');
+$stmt4 = $db->prepare('CALL nores()');
+$stmt4->execute();
+var_dump($stmt4->fetchAll());
+$db->exec('DROP PROCEDURE IF EXISTS nores');
+
+$db->exec('DROP PROCEDURE IF EXISTS ret');
+$db->exec('CREATE PROCEDURE ret() BEGIN SELECT first FROM test WHERE first=7; END;');
+$stmt5 = $db->prepare('CALL ret()');
+$stmt5->execute();
+var_dump($stmt5->fetchAll());
+$stmt5->nextRowset(); // needed to fetch the empty result set of CALL
+var_dump($stmt5->fetchAll());
+$db->exec('DROP PROCEDURE IF EXISTS ret');
+
+/* With emulated prepares */
+print("Emulated prepares\n");
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
+
+$stmt = $db->prepare('DELETE FROM test WHERE first=8');
+$stmt->execute();
+var_dump($stmt->fetchAll());
+
+$res = $db->query('DELETE FROM test WHERE first=9');
+var_dump($res->fetchAll());
+
+$stmt2 = $db->prepare('DELETE FROM test WHERE first=10');
+$stmt2->execute();
+foreach($stmt2 as $row){
+ // expect nothing
+}
+
+$stmt3 = $db->prepare('DELETE FROM test WHERE first=11');
+$stmt3->execute();
+var_dump($stmt3->fetch(PDO::FETCH_ASSOC));
+
+$stmt = $db->prepare('SELECT first FROM test WHERE first=12');
+$stmt->execute();
+var_dump($stmt->fetchAll());
+
+$db->exec('DROP PROCEDURE IF EXISTS nores');
+$db->exec('CREATE PROCEDURE nores() BEGIN DELETE FROM test WHERE first=13; END;');
+$stmt4 = $db->prepare('CALL nores()');
+$stmt4->execute();
+var_dump($stmt4->fetchAll());
+$db->exec('DROP PROCEDURE IF EXISTS nores');
+
+$db->exec('DROP PROCEDURE IF EXISTS ret');
+$db->exec('CREATE PROCEDURE ret() BEGIN SELECT first FROM test WHERE first=14; END;');
+$stmt5 = $db->prepare('CALL ret()');
+$stmt5->execute();
+var_dump($stmt5->fetchAll());
+$stmt5->nextRowset(); // needed to fetch the empty result set of CALL
+var_dump($stmt5->fetchAll());
+$db->exec('DROP PROCEDURE IF EXISTS ret');
+
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
+$db->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
+
+$stmt = $db->prepare('DELETE FROM test WHERE first=15');
+$stmt->execute();
+var_dump($stmt->fetchAll());
+
+$stmt = $db->prepare('SELECT first FROM test WHERE first=16');
+$stmt->execute();
+var_dump($stmt->fetchAll());
+
+?>
+--CLEAN--
+<?php
+require __DIR__ . '/mysql_pdo_test.inc';
+MySQLPDOTest::dropTestTable();
+?>
+--EXPECT--
+array(0) {
+}
+array(0) {
+}
+array(0) {
+}
+bool(false)
+array(1) {
+ [0]=>
+ array(2) {
+ ["first"]=>
+ int(5)
+ [0]=>
+ int(5)
+ }
+}
+array(0) {
+}
+array(1) {
+ [0]=>
+ array(2) {
+ ["first"]=>
+ int(7)
+ [0]=>
+ int(7)
+ }
+}
+array(0) {
+}
+Emulated prepares
+array(0) {
+}
+array(0) {
+}
+bool(false)
+array(1) {
+ [0]=>
+ array(2) {
+ ["first"]=>
+ string(2) "12"
+ [0]=>
+ string(2) "12"
+ }
+}
+array(0) {
+}
+array(1) {
+ [0]=>
+ array(2) {
+ ["first"]=>
+ string(2) "14"
+ [0]=>
+ string(2) "14"
+ }
+}
+array(0) {
+}
+array(0) {
+}
+array(1) {
+ [0]=>
+ array(2) {
+ ["first"]=>
+ int(16)
+ [0]=>
+ int(16)
+ }
+}