diff --git a/NEWS b/NEWS index c7fe41fd883..dab2198b9b7 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 dbac3741710..58711459ae6 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 00000000000..86e171d302d --- /dev/null +++ b/ext/pdo_mysql/tests/bug80458.phpt @@ -0,0 +1,186 @@ +--TEST-- +Bug #80458 PDOStatement::fetchAll() throws for upsert queries +--SKIPIF-- + +--FILE-- +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-- + +--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) + } +}