php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #46144 failed prepare() does not fill error and errno fields
Submitted: 2008-09-21 13:49 UTC Modified: 2009-09-20 17:29 UTC
Votes:1
Avg. Score:5.0 ± 0.0
Reproduced:0 of 0 (0.0%)
From: Progman2002 at gmx dot de Assigned:
Status: Wont fix Package: MySQLi related
PHP Version: 5.2CVS-2009-01-25 (snap) OS: Linux
Private report: No CVE-ID: None
Have you experienced this issue?
Rate the importance of this bug to you:

 [2008-09-21 13:49 UTC] Progman2002 at gmx dot de
Description:
------------
If you create a prepared statement with a DELETE query and tries to create a second prepared statement with an INSERT query on the same table without closing the first one the MySQLi::prepare() method failed, but the fields $error and $errno (and all other related to them) aren't filled with the error message. This is strange as the prepare() failed but you dont know why.

The mysql error is shown if I save the second statement into another variable (like if (!$stmt2 = $db->prepare($sql))) (maybe its related to bug #44766)

Reproduce code:
---------------
Code is at http://nopaste.php-quake.net/51976

Expected result:
----------------
PHP-Version: 5.2.6-pl7-gentoo
MySQL-Server-Version: 50042
MySQL-Protocol: 10
{Showing a MySQL error which says "Syntax error near WHERE (?,?)" or says sth. like "unfinished prepare statement before")

Actual result:
--------------
PHP-Version: 5.2.6-pl7-gentoo
MySQL-Server-Version: 50042
MySQL-Protocol: 10
-0-

(so the values are all empty strings or zero)

Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2008-11-29 10:59 UTC] Progman2002 at gmx dot de
As the paste on the pasteboard is gone I'll add the code here.
-----------

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

$db = @new MySQLi('localhost', '', '', 'test');
if (mysqli_connect_errno()) {
    die('Konnte keine Verbindung zu Datenbank aufbauen, MySQL meldete: '.mysqli_connect_error());
}
echo 'PHP-Version: '.PHP_VERSION."\n";
echo 'MySQL-Server-Version: '.$db->server_version."\n";
echo 'MySQL-Protocol: '.$db->protocol_version."\n";
$sql = 'CREATE TEMPORARY TABLE SomeTest(UserID INT NOT NULL, RechtID INT NOT NULL)';
if (!$db->query($sql)) {
    die($db->error);
}
$sql = 'DELETE FROM
            SomeTest
        WHERE
            UserID = ?';
if (!$stmt = $db->prepare($sql)) {
    die($db->error);
}
// note the missing $stmt->close() here
$sql = 'INSERT INTO
            SomeTest(UserID, RechtID)
        WHERE
            (?,?)';
if (!$stmt = $db->prepare($sql)) {
    die($db->error."-".$db->errno."-".$db->info);
}
echo "done";
?>
 [2009-01-25 10:50 UTC] Progman2002 at gmx dot de
The bug is still not fixed. Maybe it has something to do with an uncalled destructor since I use the same variable $stmt.

Actual result:
--------------
PHP-Version: 5.2.9-dev
MySQL-Server-Version: 50060
MySQL-Protocol: 10
-0-

Expected result:
----------------
PHP-Version: 5.2.9-dev
MySQL-Server-Version: 50060
MySQL-Protocol: 10
(Showing a MySQL error which says "Syntax error near WHERE (?,?)" or
says sth. like "unfinished prepare statement before")
 [2009-05-18 02:17 UTC] felix9x at yahoo dot com
It's because the first $stmt object is destroyed by the second assignment (which clears the last error message).

$sql = 'INSERT INTO
            SomeTest(UserID, RechtID)
        WHERE
            (?,?)';
if (!$stmt = $db->prepare($sql)) {
    die($db->error."-".$db->errno."-".$db->info);
}


Its equivalent to doing this: $stmt = false;
The destructor of the Mysqli_stmt class resets the Last error.

Its possible to call $stmt->close() explicitly. Probably best to use this syntax:

$sql = 'INSERT INTO
            t(i)
        WHERE
            (?)';
$stmt = $db->stmt_init();
if(!$stmt->prepare($sql) ){
  die( $stmt->error );
}
 [2009-05-18 14:43 UTC] Progman2002 at gmx dot de
Sure I can call close() by myself (which I normally do), but it doesn't solve the bug itself. The point is you get an error situation and don't know why.
 [2009-09-17 17:41 UTC] uw@php.net
I don't know what to do with this report because in a way it is a user error. The problem can easily be avoided by calling the destructor of the mysql_statement class before assigning false to it. A simple $stmt->close() before the second $stmt = $db->prepare() will fix it.


What happens is:

 $stmt = ... <valid_sql>
 $stmt = $mysqli->prepare('wrong_sql');

 <switch into php />
 <function_enter> 

    C API: mysql_stmt_init()
    C API: mysql_stmt_prepare() -> error
    copy error, because mysql_stmt_close() will clean it
    C API: mysql_stmt_close()

    RETURN false

 </function_enter>

 <evaluate assignment to $stmt>
    $stmt is a Prepared Statement for <valid_sql>
    destruct $stmt, because user did not clean it up!

    C API: mysql_stmt_close() 
    !!! previously saved error message gets cleaned !!!

 </evaluate assignment to $stmt>

 <assign return value to $stmt />
 <give control back to PHP script />

 var_dump($mysqli->error) -> no error message


One way to fix on the C level would be to blow up the MY_MYSQL struct and copy the error message into some safe place. But I wonder how we would know when to return the message copied into a safe place and when not... currently I can't think of a way how we would know.

It may be possible to hack something with mysqlnd and make mysqlnd behave different to libmysql (not clean/preserve error message in mysqlnd' stmt dtor) but that would be just the wrong place and really hackish....

Maybe we should set the status to "Won't fix".
 [2009-09-18 07:49 UTC] uw@php.net
After spending the evening on it, I am even more in the mood to change status to "Won't fix".

Even if we copy error messages to a safe place we need to know when to return the prepare error message. We should return the prepare error message, if any mysql C API call made between the failed prepare and the fetch of the error message by the user have succeeded. "Any" refers to all calls made implicitly by the destructor of the overwritten statement object before mysqli->prepare() returns. "Any" must not include calls made by the user after prepare(). Something simple will not work:

mysql_error():

  if (prepare_error && no_regular_error)
    clean_and_return_pepare_error;

That simple approach would not ensure that "any" is as small as we want it to be. We would need another flag that tells us if the user has made any API calls after prepare() and before fetchting the error().

And that gets you into a real mess: intercept all PHP API calls? Maybe abuse mysql->mysql->net.errno (a private member) again to check if it gets reset - every C API call resets it. If our special "prepare_error_fetch" flag is gone, the user must have made an API call after prepare. Oh, problem, no mysql->mysql struct available in the statement destructor that calls mysql_stmt_close()... what about introducing a new global structure for tracking to be able to link statements and connections, *ouch* -> "Won't fix"...
 [2009-09-18 10:14 UTC] Progman2002 at gmx dot de
Aren't the prepared statement saved in different MYSQL_STMT structs? If so why do one mysql_stmt_close() in one struct clear the last_error field in the other one? Or is the destructor called too late (on the new created statement), after the (php-)objects are relocated/replaced/reassigned?
 [2009-09-18 10:23 UTC] uw@php.net
Simple: there aint no two statement objects. You got one statement object and one statement struct from the first successful statement. The second struct is destroyed before prepare() returns. 

The error messages is communicated to mysql_error using the connection object, the mysql struct.
 [2009-09-20 17:29 UTC] jani@php.net
Uwe, don't bother.
 
PHP Copyright © 2001-2021 The PHP Group
All rights reserved.
Last updated: Fri Dec 03 14:03:35 2021 UTC