3

I'm trying to solve what seems to be quite an obscure problem, which I think might be a bug in either the mysqlnd driver or mysql, or could be on the PHP side. I'm hoping someone will be able to confirm if this is a bug, or if I'm doing something wrong (which is probably more likely!). I'm using php 5.6.11-1ubuntu3 and MariaDB/mysql version 10.0.20-MariaDB-0ubuntu0.15.04.1 on Ubuntu 15.10.

The code I'll post is proof of concept and isn't the original code I've had issues with - it is for illustrative purposes only, though it does run.

Please note, this is /not/ a problem calling stored procedures generally and getting the results. I know that a stored proc will return multiple results sets which need processing, and this is handled. The problem is specifically when a stored procedure uses a cursor.

I've created the following POC php code to call a stored procedure via a prepared statement in mysqli:

ini_set("display_errors", 1);
ini_set("display_startup_errors", 1);
ini_set("error_reporting", E_ALL);
ini_set("log_errors", true);
error_reporting("E_ALL");

/* Trying to find a solution to this bug:
 * mysqli used to connect
 * call stored proc via a prepared stmt.  Proc contains a cursor.
 * Connection is lost during processing, possibly at the point cursor is opened.
 */

function dbg()
{
    foreach( func_get_args() as $a )
    {
        if( !is_string($a) ) $a = var_export($a, true);
        echo "\n<br/>\nDBG: " . $a;
        ob_flush();
        flush();
    }
}

function sdown()
{
    dbg("SHUT DOWN");
}

function err($num, $err)
{
    dbg("EH: $err");
}

register_shutdown_function("sdown");
set_error_handler(err);

try
{
    test();
}
catch( Exception $e )
{
    dbg("Exception caught: " . $r->getMessage());
}
function test()
{
    dbg("Connecting...");
    $db = new mysqli("localhost", "testuser", "testtesttest", "testdb");
    dbg("Connected");
    if( $db->connect_error )
        throw new Exception("Failed connecting to DB server");
    dbg("set charset");
    $db->set_charset("utf8-bin");

    $stmt = $db->stmt_init();
    dbg("init");
    $result = $stmt->prepare("call curTest(4,5)");
    dbg("prepared");
    $stmt->execute();
    dbg("executed");
    $stmt->store_result();
    $stmt->bind_result($a, $b);
    dbg("bound");
    do {
        dbg("About to fetch...");
      $result = $stmt->fetch();
      dbg("fetched");
      dbg("RES:", $result);
      if( $result == false )
      {
        dbg(" ** ERROR: " . $db->error);
        exit;
      }
      dbg("Got vars:", $a, $b);
      $result->free();
    } while ($stmt->more_results() && $stmt->next_result());

}

The stored procedure being called looks like this:

create procedure curTest (AuthTok char(32), LogId char(11))
begin

  declare vBC varchar(10);
  declare vBID, done int unsigned;
  declare cur1 cursor for select Id, BookingCode from Bookings;
  declare continue handler for not found set done=true;

  drop table if exists tBC;
  create temporary table tBC (bid int unsigned, bc varchar(10));

  insert into tBC (bid, bc) values (42, "foo");

  open cur1;

   set done = false;
   read_loop: loop
     fetch cur1 into vBID, vBC;
     if done then
       leave read_loop;
     end if;
     insert into tBC (bid, bc) values (vBID, vBC);
   end loop;

  close cur1;

  select * from tBC;
  drop table tBC;

end

When it runs the procedure, this output is displayed on screen. Note the EH line which tells me something serious has gone wrong with the protocol whilst talking to the server. I actually lose connection at this point, which isn't shown in this POC code. Also note that it is the very first "fetch" which goes wrong; this isn't a problem with "multiple result sets".

DBG: Connecting...
DBG: Connected
DBG: set charset
DBG: init
DBG: prepared
DBG: executed
DBG: bound
DBG: About to fetch...
DBG: EH: Packets out of order. Expected 1 received 5. Packet size=15
DBG: fetched
DBG: RES:
DBG: false
DBG: ** ERROR:
DBG: SHUT DOWN

If I run this from the mysql client, I receive the expected results:

MariaDB [hb]> call curTest(1,2);
+------+----------+
| bid  | bc       |
+------+----------+
|   42 | foo      |
|    1 | PCRFXDVX |
+------+----------+
2 rows in set (0.07 sec)

Query OK, 0 rows affected (0.09 sec)

If the stored procedure is edited to only have the cursor open and close and no cursor loop, I still get the same error.

But if it is edited so that the cursor is not opened or closed at all, then it works fine:

create procedure curTest (AuthTok char(32), LogId char(11))
begin

  declare vBC varchar(10);
  declare vBID, done int unsigned;
  declare cur1 cursor for select Id, BookingCode from Bookings;
  declare continue handler for not found set done=true;

  drop table if exists tBC;
  create temporary table tBC (bid int unsigned, bc varchar(10));

  insert into tBC (bid, bc) values (42, "foo");

--   open cur1;
-- 
--    set done = false;
--    read_loop: loop
--      fetch cur1 into vBID, vBC;
--      if done then
--        leave read_loop;
--      end if;
--      insert into tBC (bid, bc) values (vBID, vBC);
--    end loop;
-- 
--   close cur1;

  select * from tBC;
  drop table tBC;

end

This gives me a nice clean run:

DBG: Connecting...
DBG: Connected
DBG: set charset
DBG: init
DBG: prepared
DBG: executed
DBG: bound
DBG: About to fetch...
DBG: fetched
DBG: RES:
DBG: true
DBG: Got vars:
DBG: 42
DBG: foo
DBG: SHUT DOWN

I have googled a lot for this and keep finding dead ends. The closest matches I could find is where people experienced similar with PDO and fixed it by using the "emulate prepares" PDO setting. The feeling was that there is a driver problem. mysqli doesn't seem to have an equivalent setting.

So I'm wondering -

  1. Has anyone here experienced this?

  2. Is there a setting or a step I'm missing which would make this work?

Thanks very much in advance!

Thanks,

-Oli

Oli Comber
  • 311
  • 2
  • 11
  • 1
    I do not have experience with this, so I'm not really able to help but since you mention people being able to work around this by using PDO, why not try PDO too? – rlanvin Jan 18 '16 at 13:46
  • It's a good suggestion, and one I'll try once I've exhausted all other avenues. My database / stored proc wrapper does a fair amount of stuff around the actual calling of procs to make the results sets nicer and more meaningful, with much other mixed in logic - so moving to PDO would potentially be a fairly extensive rewrite, which I'd like to avoid if possible. Also, if this is a bug, I'd like to confirm it and report it so it can be fixed to help other people out, too. – Oli Comber Jan 18 '16 at 13:54
  • 1
    As you have appreciated by now, PHP `prepared queries` get 'confused' when trying to get more than one result set from a procedure. imo, It is a PHP `prepared query` issue. Using `mysqli->query` to call the procedure works correctly. Even when using `next_result();`. – Ryan Vincent Jan 19 '16 at 05:29
  • I get the same errors in Windows, PHP 5.3.29 and mysql 5.5.16 ;-/ It looks to be a PHP `prepared query` issue. Debugged with seeing what got written to the database (`General Log`) and logging statements from inside the procedure to ensure what runs when - [CaptTofu/Stored-procedure-debugging-routines](https://github.com/CaptTofu/Stored-procedure-debugging-routines) – Ryan Vincent Jan 19 '16 at 11:28
  • Thanks for confirming, Ryan, I'm glad I'm not going mad! - I'll raise a bug with the php guys :-) – Oli Comber Jan 19 '16 at 16:21

0 Answers0