Edit report at http://bugs.php.net/bug.php?id=44454&edit=1
ID: 44454 Updated by: fel...@php.net Reported by: mfisc...@php.net Summary: Unexpected exception thrown in foreach() statement -Status: Verified +Status: Assigned Type: Bug Package: PDO related Operating System: * PHP Version: 5.*, 6CVS (2009-04-25) -Assigned To: +Assigned To: mysql Previous Comments: ------------------------------------------------------------------------ [2010-06-03 20:58:10] rgagnon24 at gmail dot com I've attached a patch "fix-mysql_statement.c-5.2.13.patch" to resolve this problem. From: http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html "When used after mysql_store_result(), mysql_fetch_row() returns NULL when there are no more rows to retrieve. When used after mysql_use_result(), mysql_fetch_row() returns NULL when there are no more rows to retrieve or if an error occurred" The patch simply does not raise an exception during a NULL result from mysql_fetch_row() since it only indicates the exhaustion of data. The condition added simply matches the use of either mysql_use_result() or mysql_store_result() called in pdo_mysql_stmt_execute(). When not buffered, mysql_use_result() is called. Therefore, the same check is performed after the fetch before deciding to raise an exception. Also, when un-buffered queries are used, the test script above would be invalid as you cannot perform another operation on a result-set when not all of the results have been retrieved. The patch was created against the released PHP 5.2.3 source code tarball. It's so small, you should be able to modify it easily for any version of the mysql_statement.c file. ------------------------------------------------------------------------ [2010-06-03 20:36:48] rgagnon24 at gmail dot com From: http://dev.mysql.com/doc/refman/5.0/en/mysql-errno.html "Note that some functions like mysql_fetch_row() don't set mysql_errno() if they succeed." And: http://dev.mysql.com/doc/refman/5.0/en/mysql-fetch-row.html "Note that error is not reset between calls to mysql_fetch_row()" ----------------------------------------------------------------------------- Since all the SELECT'd rows are fetched ok, the error from the botched insert is still hanging around for mysql_errno() to find, and raise the exact same exception after the data is finished being iterated. ------------------------------------------------------------------------ [2010-06-03 20:03:37] rgagnon24 at gmail dot com Looking at pdo_mysql extension source code, I see the exception is actually being raised twice, not being buffered. from php5.2.13 source, file ext/pdo_mysql/mysql_statement.c is calling _pdo_mysql_error() in two places. Once from line 218 in pdo_mysql_stmt_execute() after mysql_real_query() fails, and then again at line 425 in pdo_mysql_stmt_fetch() because mysql_errno() indicates there is an error. ------------------------------------------------------------------------ [2010-05-08 06:08:33] gregory at tiv dot net Correction: ----------- if ( $conn->errorCode() ) { should be if ( $conn->errorCode() !== '00000' ) { ------------------------------------------------------------------------ [2010-05-07 02:13:48] gregory at tiv dot net I have a simpler test case, one solution/explanation and one workaround. Tested under: Windows - PHP 5.2.13 (cli) (built: Feb 24 2010 14:32:32) FreeBSD - PHP 5.2.12 with Suhosin-Patch 0.9.7 (cli) (built: Feb 24 2010 23:12:45) Demonstration code: ------------------- <?php # # PDO foreach exception bug # Demonstration code # Author: Gregory Karpinsky, http://www.tiv.net # 2010-05-06 print '<p>This code works OK (Exception is cleaned artificially)</p>'; $conn = new PDO( 'mysql:host=localhost', 'test', 'test' ); $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $oRS = $conn->query( "select 'OK'" ); foreach ( $oRS as $row ) { try { $conn->query( 'Bogus SQL' ); } catch (PDOException $e) {} if ( $conn->errorCode() ) { $conn->query( "select 'CLEAN_PDO_ERROR'" ); } print '<p>NO exception will be thrown.</p>'; } print '<p>This code works OK (two separate connections)</p>'; $conn = new PDO( 'mysql:host=localhost', 'test', 'test' ); $conn2 = new PDO( 'mysql:host=localhost', 'test', 'test' ); $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $conn2->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $oRS = $conn->query( "select 'OK'" ); foreach ( $oRS as $row ) { try { $conn2->query( 'Bogus SQL' ); } catch (PDOException $e) {} print '<p>NO exception will be thrown.</p>'; } print '<p>This code throws unexpected exception in foreach</p>'; $conn = new PDO( 'mysql:host=localhost', 'test', 'test' ); $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $oRS = $conn->query( "select 'OK'" ); foreach ( $oRS as $row ) { try { $conn->query( 'Bogus SQL' ); } catch (PDOException $e) {} print '<p>Exception will be thrown after this...</p>'; } ?> ------------------------------------------------------------------------ The remainder of the comments for this report are too long. To view the rest of the comments, please view the bug report online at http://bugs.php.net/bug.php?id=44454 -- Edit this bug report at http://bugs.php.net/bug.php?id=44454&edit=1