using mysqli::prepare within an already established connection using prepare? - php

Quick question. Whilst using the prepare method in mysqli. It is possible/a good idea; to use it twice within the same mysqli connection? Example:
OOP layered
public function getStuff(){
$posts=array();
$query = $this->DBH->prepare('SELECT * FROM table WHERE stuff =?');
$query->bind_param('s','param');
$query->execute();
$query->bind_result($ID,$col1,$col2,$etc);
while($query->fetch()){
$posts[]=array('ID'=>$ID,'col1'=>$col1,'extras'=>$this->getExtras($ID));
}
$query->close();
return $posts;
}
private function getExtra($postID){
$extras=array();
$query = $this->DBH->prepare('SELECT * FROM anotherTable WHERE moreStuff =?');
$query->bind_param('s',$postID);
$query->execute();
$query->bind_result($ID,$col1,$col2,$etc);
while($query->fetch()){
$extras[]=array('ID'=>$ID,'col1'=>$col1,'etc'=>$etc);
}
$query->close();
return $extras;
}
Right my possible error is that I've used the same variable and the same database connection. I'm not 100% sure this will work as I've called $this->DBH whilst it is already being used in the parent function. Is there a better method to what I'm trying to achieve or is there a better structure I can use. Or should I just give up and use a separate variable? lol
Hopeful outcome:
$posts=array('ID'=>'column ID number','col1'=>'column1 data', 'extras'=>array('ID'=>'second table\'s ID number','col1'=>'second tables data','etc'=>'etc etc etc'));

In your example above, the variables which matter are $query. Each of those is local to its own method, and so the variables themselves will not collide. The MySQLi connection $this->DBH is capable of handling multiple open statements at once if circumstances are right.
The place where you need to use caution is with their execution order. If you prepare and execute a statement but do not fetch all rows from it, you may not be able to prepare() the next one until all rows have been fetched unless you first close it with mysqli_stmt::close() to deallocate the open statement handle.
For example:
// Prepares successfully:
$s1 = $mysqli->prepare("SELECT * FROM t1");
// Also prepares successfully (previous one not executed)
$s2 = $mysqli->prepare("SELECT * FROM t2");
// Then consider:
$s1 = $mysqli->prepare("SELECT id, name FROM t1");
$s1->bind_result($id, $name);
$s1->execute();
// And attempt to prepare another
$s2 = $mysqli->prepare("SELECT id, name FROM t2");
// Fails because $s1 has rows waiting to be fetched.
echo $m->error;
// "Commands out of sync; you can't run this command now"
Edit: misread your example...
Looking at your example above, you are indeed calling getExtras() while you are fetching from the getStuff() statement. You may run into the issue described above. Your two operations in this case may be able to be handled with a single JOIN instead, from which you fetch in only one loop to populate all your variables and build your output array as you want it. Depending on your need, this should either be an INNER JOIN if a related row is expected to exist in the othertable, or a LEFT JOIN if the related othertable may or may not have a row that matches the given ID.
SELECT
maintable.id,
maintable.col1,
othertable.col2,
othertable.col3
FROM
maintable
JOIN othertable ON maintable.id = othertable.id

Related

PHP: Using PDO object and multiple prepare statement affects the Statement object returned

For the sake of avoiding unnecessary information, roughly my code flows as follows:
$db = new PDO(DSN, DB_USER, DB_PW);
$sql1 = "SELECT * FROM Table1";
// fetching the first result
$stt1 = $db->prepare($sql1);
if ($stt1->execute()) {
$result = $stt1->fetch(PDO::FETCH_ASSOC);
}
// doing update in the middle by using the SAME $db object, but different statement variable
$sql2 = "UPDATE Table1 SET field1 = 'footest1' WHERE id = 1";
$stt2 = $db->prepare($sql2);
$stt2->execute();
// fetching the next result
$result = $stt1->fetch(PDO::FETCH_ASSOC);
Ok, I ran this, and to my surprise, when I am fetching my next result, I get false. Does preparing another statement ($stt2) in the middle interrupt my already created $stt1?
And I have 15+ records on that table.
Update: It seems like the execute method of the statement object is the reason my second fetch is returning false. For this to work, calling $stt1->execute() again before fetching the second time solves this problem... But this shows that there is some connection via the execute method between all the statement object?
Different databases have different constraints. This is not an issue with PHP or PDO, but with the database connection.
While you can rely on any DB connector to support at least one cursor with pending rows, many databases will limit you at one, and require you to either fully fetch or explicitly close the underlying cursor before executing a new statement.
This is indeed a property of the database connection, as all PDO statements are bound to one. (Where would they fetch the data from if they didn't remain bound to the database connection?) If you're using a database that only supports one open prepared statement at a time, you'll have no choice but to either serialize your accesses or open multiple connections to the database. You might also want to take a look at the closeCursor method of the PDOStatement class.

How to close the result set in mysqli prepared statement, but keep the statement - without buffering entire result?

I'm trying to use multiple prepared statements with the mysqli driver in PHP and getting the infamous error:
Commands out of sync; you can't run this command now
Although I think this database API is atrociously brain-damaged, I do understand why I'm getting the error, as described by this question.
Several solutions are offered in this and various other answers I've found laying around, the only ones I've seen that seem to work involve
$stmt->store_result()
which buffers the entire result set.
However, I don't want to buffer the entire result set - I want to discard it without using (a large amount of) memory to store it!
All I want to do, is create a prepared statement, use bind_param() and bind_result() and fetch() to get some of the results, and then somehow close the results so that I can reuse the statment later and run other queries until then.
I've tried just about every way of getting the actual result object being used from the statement and explicitly closing that, and I've tried calling next_result() until it returns null. Neither of those solutions prevents that error from appearing.
So, how can I close a result set and execute other prepared statements after a fetch(), without trashing the executed statement and having to re-parse the query?
$q = $dbc->prepare("SELECT id, typename, storageclass, tablename FROM _dbtype WHERE typename=?");
$q->bind_param("s", $typeName);
$q->bind_result($id,$typeName,$storageClass,$tableName);
$q->execute();
$q->fetch(); // ie. only once
... <something that does not destroy $q> ...
$q2 = $dbc->prepare("SELECT id, name FROM _storagetype");
see mysqli_stmt::free_result()
(and http://dev.mysql.com/doc/refman/5.7/en/mysql-stmt-free-result.html for the hint that "If there is a cursor open for the statement, mysql_stmt_free_result() closes it.")
<?php
mysqli_report(MYSQLI_REPORT_STRICT|MYSQLI_REPORT_ALL);
$mysqli = new mysqli('localhost', 'localonly', 'localonly', 'test');
if ($mysqli->connect_errno) {
trigger_error( sprintf('mysqli connect error (%d) %s', $mysqli->connect_errno, $mysqli->connect_error), E_USER_ERROR);
die;
}
$mysqli->query('CREATE TEMPORARY TABLE sofoo ( id int auto_increment, primary key(id))');
$mysqli->query('INSERT INTO sofoo VALUES (),(),(),(),(),()');
$stmtA = $mysqli->prepare('SELECT id FROM sofoo WHERE id>1');
$stmtB = $mysqli->prepare('SELECT id FROM sofoo WHERE id<10 ORDER BY id DESC');
$stmtA->execute();
$stmtA->bind_result($id);
$stmtA->fetch(); echo $id, PHP_EOL;
$stmtA->free_result(); // without this the script ends with "Fatal error: Uncaught mysqli_sql_exception: Commands out of sync;"
$stmtB->execute();
$stmtB->bind_result($id);
$stmtB->fetch(); echo $id, PHP_EOL;
If there are multiple result sets (i.e. if you have to use next_result()) you must call free_result()for each result set separately.

Should I define my SQL statement statement handle using separate variables in PHP?

Are all 3 options the same (Y/N) or is one better (A/B/C)?
Option A - (1) defining the SQL string in the variable $sql and (2) define the statement handle using the "method" prepare on the variable $sql to compose the statement and (3) activating composition of the statement in step 2 using the execute function.
$sql = "SELECT * FROM table1";
$sth = $dbh->prepare($sql);
$sth->execute();
Option B - Similar to Option C because the ->query method is used directly on the db object and similar to Option A in that the sql statement is kept separate.
$sql = "SELECT * FROM table1";
$sth = $dbh->query($sql);
Option C - The statement handle is the sql query itself (no reference to any another variables and just using one method.
$sth = $dbh->query("SELECT * FROM table1");
Questions:
Do both options yield the same result?
If they yield the same result, is one approach recommended (i.e. best practice)?
Did I get the vocabulary like "variable", "method", and "function" correct?
Does Option A still query the db despite not explicitly using the ->query() method?
What are the pros/cons of including the sql stmnt in a seperate variable vs in the PDO Statement?
I would say both methods are the same about using a string variable or direct text string.
Assuming you using PDO there is difference between prepare statement and invoke a query statement.
In case A, the prepare statement will make the database create a plan so that statement can be reexecuted without reparsing the query string.
In case B the query is executed at once.
In your given example case B would run a little bit faster.
But if your statement uses arguments case A would benefit you with additional security due to placeholders been replaced by the driver.
Do both options yield the same result?
Yes.
If they yield the same result, is one approach recommended (i.e. best practice)?
Both are fine. In your particular example Option B gets the same job done with less code.
However, if you need to use parameters and/or constraints in your query (e.g. ...WHERE id = :id) you need to opt for option A to bind params using the $dbh->bindParam(':id', $id, PDO::PARAM_INT) method after your prepare your statement, e.g:
$dbh->prepare('UPDATE table SET column = :value WHERE id = :id');
$dbh->bindParam(':value', $someNewValue, PDO::PARAM_STR);
$dbh->bindParam(':id', $targetId, PDO::PARAM_INT);
$dbh->execute();
Doing it this way will also protect you from nasty things like SQL injections.
Storing the query in a separate variable is only needed if you plan to re-use that query at a later time in your code. Otherwise you might as well just type it within your prepare/query method directly.
Did I get the vocabulary like "variable", "method", and "function" correct?
Looks about right! Except: "the execute function" is a method. Functions and methods are basically the same things except when they belong to an object they are referred to as methods. In your example execute belongs to the $sth object, so it's called a method.
Does Option A still query the db despite not explicitly using the ->query() method?
Yes. The execute method executes the query that was prepared in $dbh->prepare(...). If you want to use parameters you can call ->bindParam() between your prepare and execute methods. If you don't need parameters, invoking ->query() directly is really the more convenient way to do it.

PDO “Uncaught exception 'PDOException' .. Cannot execute queries while other unbuffered queries are active. Consider using PDOStatement::fetchAll().”

I know this question has been asked many times, but I've read the answers to many of the questions and still cannot understand why I am receiving this error:
Fatal error: Uncaught exception 'PDOException' with message
'SQLSTATE[HY000]: General error: 2014 Cannot execute queries while
other unbuffered queries are active. Consider using
PDOStatement::fetchAll(). Alternatively, if your code is only ever
going to run against mysql, you may enable query buffering by setting
the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY attribute.'
The first thing that is odd, is that I do not get an error on my localhost (wampserver), but I do get it on my web server. The php version on my localhost is 5.3.10, and on my web server it is 5.3.13.
I have read that the source of this error is making a query when data left in the buffer from a previous query. This is not the case for me -- I have echo'd out all of the data and I know for a fact that every row returned in a query is being fetched.
With that said, I have found that changing one of my queries to fetchAll instead of fetch fixes the problem, but it simply makes no since because I know that all of the rows returned are being read. When I used fetchAll for the query (it is being made in a loop), I printed out the array each loop, and only one item was in the array for each query in the loop.
One more piece of information. It's not the query that I changed to fetchAll (which makes the error go away) that throws the PDO error, there is another query later in my php file that throws the error. My file is basically like this:
... code ...
query 1
... code ...
loop
query 2
end loop
... code ...
query 3
If I comment out query 3, there is no error. If I comment out, or change to fetchAll, query 2, there is no error. query 1 has no affect whatsoever.
I would also like to add that I have tried adding LIMIT 1 to all of the queries on the page (at the same time), and the error is still there. I think this proves there is not unread data in the buffer, right?
I'm really confused, so I would appreciate your advice. Before someone asks, I can't post the full code for this, but here is a simplified version of my code:
$stmt = $this->db->prepare('SELECT ... :par LIMIT 1');
makeQuery($stmt, array(':par' => $var));
$row = $stmt->fetch(PDO::FETCH_ASSOC);
$stmt = $this->db->prepare('SELECT ... :par LIMIT 1');
for loop
makeQuery($stmt, array(':par' => $var));
$row2 = $stmt->fetch(PDO::FETCH_ASSOC);
... [use row2] ...
end for loop
$stmt = $this->db->prepare('SELECT ... :par LIMIT 1');
makeQuery($stmt, array(':par' => $var));
$row3 = $stmt->fetch(PDO::FETCH_ASSOC);
Here is makeQuery().
/**************************************************************************************************************
* Function: makeQuery *
* Desc: Makes a PDO query. *
* Pre conditions: The statement/query and an array of named parameters (may be empty) must be passed. *
* Post conditions: The PDO query is executed. Exceptions are caught, displayed, and page execution stopped. *
**************************************************************************************************************/
function makeQuery($stmt, $array, $errMsg = '')
{
try
{
$stmt->execute($array);
}
catch (PDOException $e)
{
print $errMsg != ''?$errMsg:"Error!: " . $e->getMessage() . "<br/>";
die();
}
}
Thanks for your help!
EDIT: I also tried doing the following after query 2 (since that seems to be the source of the problem:
$row2 = $stmt->fetch(PDO::FETCH_ASSOC); var_dump($row2);
The output was:
bool(false)
Have I stumbled across a PDO bug?
You need to fetch until a row fetch attempt fails. I know you may only have one row in the result set and think one fetch is enough, but its not (when you're using unbuffered queries). PDO doesn't know how many rows there are until it reaches the end, where it tries to fetch the next row, but it fails.
You probably have other statements where you didn't fully "fetch until a fetch failed". Yes, I see that you fetch until the fetch failed for one of the statements, but that doesn't mean you did it for all of them.
To clarify -
When you execute a query via execute(), you create a result set that must be fetched from the db into php. PDO can only handle 1 of these "result set in progress of being fetched" at a time (per connection). You need to completely fetch the result set, all the way to the end of it, before you can start fetching a different result set from a different call to execute().
When you "call fetch() until a fetch() fails", the fact that you reached the end of the results is internally noted by PDO when that final call to fetch() fails due to there being no more results. PDO is then satisfied that the results are fully fetched, and it can clean up whatever internal resources between php and the db that were established for that result set, allowing you to make/fetch other queries.
There's other ways to make PDO "call fetch() until a fetch() fails".
Just use fetchAll(), which simply fetches all rows, and so it will hit the end of the result set.
or just call closeCursor()
*if you look at the source for closeCursor(), the default implementation literally just fetches the rows and discards them until it reaches the end. It's written in c obviously, but it more or less does this:
function closeCursor() {
while ($row = $stmt->fetch()) {}
$this->stmtFullyFetched = true;
}
Some db drivers may have a more efficient implementation that doesn't require them to fetch lots of rows that nobody cares about, but that's the default way PDO does it. Anyway...
Normally you don't have these problems when you use buffered queries. The reason is because with buffered queries, right after you execute them, PDO will automatically fully fetch the db results into php memory, so it does the "call fetch() until a fetch() fails" part for you, automatically. When you later call fetch() or fetchAll() yourself, it's fetching results from php memory, not from the db. So basically, the result set is immediately fully fetched when using buffered queries, so there's no opportunity to have more than 1 "result set in progress of being fetched" at the same time (because php is single threaded, so no chance of 2 queries running at the same time).
Given this:
$sql = "select * from test.a limit 1";
$stmt = $dbh->prepare($sql);
$stmt->execute(array());
Ways to fully fetch the result set (assuming you only want the first row):
$row = $stmt->fetch();
$stmt->closeCursor();
or
list($row) = $stmt->fetchAll(); //tricky
or
$row = $stmt->fetch();
while ($stmt->fetch()) {}
After struggling with this issue for days, I finally found that this worked for me:
$db = new PDO ($cnstring, $user, $pwd);
$db->setAttribute (PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, true);
This also happen if you are trying to fetch a non SELECT query (Eg - UPDATE/INSERT/ALTER/CREATE). Make sure to use fetch or fetchAll only for SELECT queries.
Possible Duplicate Answer/Question

PHP's PDO Prepare Method Fails While in a Loop

Given the following code:
// Connect to MySQL up here
$example_query = $database->prepare('SELECT * FROM table2');
if ($example_query === false) die('prepare failed');
$query = $database->prepare('SELECT * FROM table1');
$query->execute();
while ($results = $query->fetch())
{
$example_query = $database->prepare('SELECT * FROM table2');
if ($example_query === false) die('prepare failed'); //it will die here
}
I obviously attempt to prepare statements to SELET everything from table2 twice. The second one (the one in the WHILE loop) always fails and causes an error.
This is only on my production server, developing locally I have no issues so it must be some kind of setting somewhere.
My immediate thought is that MySQL has some kind of max_connections setting that is set to 1 and a connection is kept open until the WHILE loop is completed so when I try to prepare a new query it says "nope too many connected already" and shits out.
Any ideas?
EDIT: Yes I know there's no need to do it twice, in my actual code it only gets prepared in the WHILE loop, but like I said that fails on my production server so after some testing I discovered that a simple SELECT * query fails in the WHILE loop but not out of it. The example code I gave is obviously very watered down to just illustrate the issue.
Problem was that I couldn't do something while an unbuffered query was running (which it was in the WHILE loop)
Solution was to add the following line to ensure buffered queries were being used:
$database->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY,true);
There shouldn't be a need to prepare $example_query more than once. Simply execute the query inside your loop.
EDIT: If you must prepare a new query in each loop iteration, an explicit $example_query->closeCursor() at the end of the loop should free any resources associated with the statement object.

Categories