Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

2 prepared statements, 2 stored procedures, 1 mysqli connection

Problem

How to call two MySQL stored procedures in the same mysqli connection using prepared statements (or another query method equally safe against SQL injections) without getting the following errors:

Warning: Packets out of order. Expected 1 received 61. Packet size=7 in /...
Warning: mysqli::prepare(): MySQL server has gone away in /...

Got the code hooked up online at tutorialspoint

Story

I'm making a PHP backend with a MySQL database. I have two results that I want to get from one query: a list of weekly summaries, and a summary of all the weeks.

┌───────┬────────────┬────────────┬─────
|  Week |    Sales   | Commission | ...
├───────┼────────────┼────────────┼─────
| week1 |  $7,912.12 |    $923.41 | ...
| week2 |  $6,423.48 |    $824.87 | ...
| week3 |  $8,180.67 |    $634.04 | ...
|  ...  |    ...     |    ...     | ...
├───────┼────────────┼────────────┼─────
| total | $67,012.23 |  $7,532.58 | ...
| avg   |  $7,012.54 |    $787.38 | ...
└───────┴────────────┴────────────┴─────

I used to just store the weekly summaries in a database table, and used a stored procedure to get the summary of all the weekly summaries. In my PHP code, I just selected all the rows in the week table and then called the getWeeksSummary stored procedure.

Now I have to be able to filter the data in the weekly summaries. I replaced a simple SELECT ... FROM week with a stored procedure getWeeks() to calculate all the weekly summaries.

Code

$weeksSummary = new stdClass();

if ($stmt = $mysqli->prepare('CALL getWeeks(?,?,?);')) {
    $stmt->bind_param('sss', $a, $b, $c);
    $stmt->execute();
    $stmt->bind_result($week, $sales, $commission, ...);
    $weeksSummary->weeks = [];
    while($stmt->fetch())
    {
        $week = new stdClass();
        $week->week = $week;
        $week->sales = $sales;
        $week->commission = $commission;
        ...
        $weeksSummary->weeks[] = $week;
    }
    $stmt->free_result();
    $stmt->close();
}

if ($stmt = $mysqli->prepare('CALL getWeeksSummary(?,?,?);')) {
    $stmt->bind_param('sss', $a, $b, $c);
    $stmt->execute();
    $stmt->bind_result($avgSales, $totSales, $avgCommission, $totCommission ...);
    $stmt->fetch();
    $weeksSummary->summary = new stdClass();
    $weeksSummary->summary->avgSales = $avgSales;
    $weeksSummary->summary->totSales = $totSales;
    $weeksSummary->summary->avgCommission = $avgCommission;
    $weeksSummary->summary->totCommission = $totCommission;
    ...
    $stmt->free_result();
    $stmt->close();
}

echo json_encode($weeksSummary);

This code worked fine when the first prepared statement was SELECT week, sales, commission, ... FROM week WHERE a=?, b=?, c=?; instead of CALL getWeeks(?,?,?);. Now I get these errors:

Warning: Packets out of order. Expected 1 received 61. Packet size=7 in /...
Warning: mysqli::prepare(): MySQL server has gone away in /...

Attempts

1) Failed: I used a new statement object $stmt2 for the second query. Same errors.

2) Success: I closed the mysqli connection and opened a new one before the second statement. A second mysqli connection with its own prepared statement does run fine, but the code to connect to the database is kept completely separate, so that doesn't really help.

3) Failed: Just out of curiosity, I went back to my original working code and reordered the statements, putting the stored procedure statement before the SELECT statement. Same errors. So the mysqli connection is fine with queries before the stored procedure, but doesn't like anything after the stored procedure.

4) Failed: I tried putting $mysqli->next_result(); after the first statement. Same errors. However, if I use query() instead of prepare() to call the stored procedures, the next_result() does indeed allow both stored procedures to run. I'd like to use prepared statement though, as they help against SQL injections.

Undesirable Potential Solutions

A): I could separate it into two calls to the back-end, but the summaries would be out of sync on the front-end when data refreshes.

B): I could join them into one MySQL stored procedure and then separate them in PHP, but I need them separate as well, so the same code would be there twice.

C): I could stop using prepared statements, but I don't know any other ways to avoid SQL injections.

Help

Any suggestions?

like image 946
Ray Avatar asked Sep 25 '15 02:09

Ray


People also ask

What is a prepared statement in MySQL?

The MySQL database supports prepared statements. A prepared statement or a parameterized statement is used to execute the same statement repeatedly with high efficiency and protect against SQL injections. The prepared statement execution consists of two stages: prepare and execute.

What is PHP mysqli procedural prepared statement?

PHP MYSQLi Procedural Prepared Statements for beginners PHP MYSQLi Prepared Statement is one of the best way to execute same statement repeatedly with high efficiency. But it requires additional steps and functions to execute query which sometimes confuse most of the php beginners.

Which statement is used to execute a stored procedure in MySQL?

The CALL SQL statement is used to execute a stored procedure. Parameter. Stored procedures can have IN, INOUT and OUT parameters, depending on the MySQL version. The mysqli interface has no special notion for the different kinds of parameters.

What is the syntax for PHP prepared statements?

SQL syntax for PHP prepared statements cannot be nested. This means that a statement that is passed to a “PREPARE” statement cannot also be a “PREPARE”, “EXECUTE”, or “DEALLOCATE PREPARE” statement. Prepared statements can be used in stored procedures (function for calling complete sets of statements).


2 Answers

Well, I'll try to answer for the question title, assuming that in the first statement not a regular query but one of two aforementioned stored procedures were called.

After calling a stored procedure, you always have to move over additional empty result set returned by every stored procedure:

$mysqli->next_result();

Also, after first prepared function call, add one extra fetch() after getting your data:

$stmt->fetch();
$stmt->free_result();

as you have to "free" the result set waiting on the server side. It could be done in many ways, but simplest would be just calling fetch() one more time, or, more strictly put, you have to call fetch() until it returns FALSE, indicating that there are no more rows left in the resultset. You are doing it [silently] in the other snippets, when calling fetch() in the while loop, but here, fetching only one row, you have to call it explicitly.

There is another way, way more convenient: use get_result() (if available) which will solve all your problems at once. Instead of that long and windy code you have at the moment, only four lines actually needed:

$stmt = $mysqli->prepare('CALL getWeeksSummary(?,?,?)');
$stmt->bind_param('sss', $a, $b, $c);
$stmt->execute();
$weeksSummary = $stmt->get_result()->fetch_object();

get_result() will free that waiting resultset and at the same time allow you to use fetch_object() method, which will let you to get the resulting object in just one line.

like image 103
Your Common Sense Avatar answered Nov 06 '22 17:11

Your Common Sense


Reading the mysqli documentation, it says that $stmt->free_result() is to free the memory allocated from $stmt->store_result(). Since the code doesn't use store_result(), removing free_result() solves the errors.

Of course, it also says to use store_result() whenever a query returns a result set. I don't really understand why (something to do with buffering), but since these two prepared statements and stored procedures work without store_result(), the problem is solved.

I'm still curious why it doesn't work with store_result() and free_result(), but at least there is some working code now. Here is the modified code at tutorialspoint.


As a side note, instead of using two prepared statements with two stored procedures, a way around it is to use one prepared statement to set variables

$stmt = $mysqli->prepare('SET @a = ?, @b = ?, @c = ?')
...

then use those variables in queries to call the stored procedures

$result = $mysqli->query('CALL getWeeks(@a,@b,@c)')
...
$result = $mysqli->query('CALL getWeeksSummary(@a,@b,@c)')
...
like image 25
Ray Avatar answered Nov 06 '22 16:11

Ray