I've run into an issue that I'm hoping to get a little help on. I'm using the following:
Kohana 3.0.7
PostgreSQL 8.4
Transactions in PostgreSQL using
$db->query(NULL, 'BEGIN', FALSE)
$db->query(NULL, 'ROLLBACK', FALSE);
$db->query(NULL, 'COMMIT', FALSE);
The issue is that when I send a query to the database that results in a postgres error within a transaction my system freezes up. When I send the same query to the database without wrapping it in a transaction the PDO error is reported back just as expected. Here is an exmaple:
This first example works fine and duplicate key value violates unique constraint "pk_test_table" error is returned:
$query = DB::query(Database::INSERT, 'INSERT INTO test_table (test_table_id, test_table_val) VALUES (:id, :value)';
$query->param(':id', 1);
$query->param(':value', "test value");
try
{
$result = $query->execute($db);
}
catch (Exception $e)
{
echo 'Caught exception: ', $e->getMessage(), "\n";
}
This second example causes my system to freeze (I can't tell if it's an infinite loop, or some other freeze):
$db->query(NULL, 'BEGIN', FALSE);
$query = DB::query(Database::INSERT, 'INSERT INTO test_table (test_table_id, test_table_val) VALUES (:id, :value)';
$query->param(':id', 1);
$query->param(':value', "test value");
try
{
$result = $query->execute($db);
}
catch (Exception $e)
{
echo 'Caught exception: ', $e->getMessage(), "\n";
}
$db->query(NULL, 'ROLLBACK', FALSE);
As you can see the only difference is that the second example is wrapped in a transaction.
Any ideas on what is going on? Any suggestions for things to try?
I found a way to work around the issue. Not sure if this is a bug in PDO or some other part of the tool set but what I'm doing to work around is the following:
$exception_exists = FALSE;
$db->query(NULL, 'BEGIN', FALSE);
$query = DB::query(Database::INSERT, 'INSERT INTO test_table (test_table_id, test_table_val) VALUES (:id, :value)';
$query->param(':id', 1);
$query->param(':value', "test value");
try
{
$result = $query->execute($db);
}
catch (Exception $e)
{
echo 'Caught exception: ', $e->getMessage(), "\n";
$exception_exists = TRUE;
}
if (!$exception_exists)
{
$db->query(NULL, 'ROLLBACK', FALSE);
}
By adding the variable $exception_exists in the catch I can then act of that if there is no exception. If there is an exception and I try to ROLLBACK or COMMIT then I get the freezing behavior.
This works for now but I wouldn't call it elegant.
Related
I'm refactoring some code which consists in multiple insert queries, to do so I'm converting every insert method that looks like this:
$code = Yii::$app->db->createCommand()
->insert('code', $codeData
)->execute();
To something like this:
$codeQuery = Yii::$app->db->createCommand()
->insert('code', $codeData);
return $codeQuery;
And in an another method which executes the transaction:
$transaction = Yii::$app->db->beginTransaction();
try{
Yii::$app->db->createCommand($codeQuery)->execute();
$transaction->commit();
} catch (\Exception $e) {
$transaction->rollBack();
throw $e;
} catch (\Throwable $e) {
$transaction->rollBack();
throw $e;
}
But I got the error:
Object of class yii\db\Command could not be converted to string
This post: post doesn't really helped me.
I'm doubtful if that correct choice is to rewrite every single insert command in something that the transaction method can execute, or converting directly my current insert command output in something viable.
If you already had assigned $codeQuery = Yii::$app->db->createCommand()->insert('code', $codeData) than you don't need to pass $codeQuery into another createCommand method. This method can read string of sql as variable.
If you want to transform command into string, make a conversion:
$str = $codeQuery->getRawSql(); // get sql statement
$res = $codeQuery->execute(); // execute sql statement
From mysql table at first want to delete row. Only after the row deleted, i want to select sum(certain_column). Want to be sure that until row deleted, i do not execute select.
Decided to use such code like this:
try{
$db->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
$db->beginTransaction();
try{
$stmt_delete = $db->prepare('DELETE FROM `table` WHERE `Id` = 1;');
$stmt_delete_items->execute( );
}
catch (PDOException $e){
echo "DataBase Error: " .htmlspecialchars( $e->getMessage() , ENT_QUOTES, "UTF-8").'<br>';
$db->rollBack();
exit;
}
try{
$stmt_select = $db->prepare('SELECT SUM(`Quantity`) AS `SumQuantity` FROM `table` WHERE `some_column` = 234;');
$stmt_select->execute( );
$arr_select = $stmt_select->fetchAll(PDO::FETCH_ASSOC);
}
catch (PDOException $e){
echo "DataBase Error: " .htmlspecialchars( $e->getMessage() , ENT_QUOTES, "UTF-8").'<br>';
}
$db->commit();
}
Expecting at first execute $stmt_delete and only then $stmt_select. In usual case i get such execution order. But is it guaranteed 100%? Somewhere read that delete may take more time than select... with exit; i prevent next execution only in case of error. But i need to delay $stmt_select until $stmt_delete.
Or may be better to use START TRANSACTION; ... COMMIT;. Or may be for DELETE to use LOCK TABLES ... UNLOCK TABLES.
Please advice solution to be 100% sure that select executes only after delete is finished.
I need to insert multiple user from an Excel file using Symfony3 command. I have read the following article about batch processing: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/batch-processing.html
I have been wondering if there is a way to no stop the flushing process when a query fails (for a not null column for instance). I would actually like to be able to not check all my data before doing the persist and let Doctrine continue the inserts, even though a query failed in the flush of 20 queries lets say.
Thank you for your help.
Kind regards,
This template may help you go further...
$batchSize = 20;
$currentSize = 0;
$data = [ .... ];
foreach ($data as $item) {
$entity = new Entity();
$entity->setProperty($item['property']);
try {
$currentSize++;
$em->persist($entity);
if ($batchSize% $currentSize === 0) {
$em->flush();
$em->clear();
}
} catch (\Doctrine\ORM\ORMException $e) {
$currentSize--;
}
}
$em->flush();
$em->clear();
public function smart_query($query, $options = null, $bindoptions = null)
{
// Code to Reconnect incase of timeout
try {
$this->db->query('SELECT * FROM templates');
}
catch (PDOException $e)
{
echo $e;
$pdooptions = array(
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
);
$this->db = new PDO("mysql:host=localhost;dbname=$this->database", "$this->username", "$this->password", $pdooptions);
}
$this->statement = $this->db->prepare($query);
if($bindoptions != null)
{
$this->bind($bindoptions);
}
$this->execute();
if($options != null)
{
// Return Single Row
if($options['rows'] == 1)
{
return $this->statement->fetch(PDO::FETCH_ASSOC);
}
// Return Multiple Rows
elseif($options['rows'] != 1)
{
return $this->statement->fetchAll(PDO::FETCH_ASSOC);
}
}
}
I've saw this code today, and got really confused.
It looks like he is trying to process a simple query, before doing the actual query.
Why is he checking if the connection is still open?
I thought that PDO only destroys it's connection upon script finishing executing automatically?
Is that correct to check if it's open or closed?
This implements a form of lazy loading.
This first time a query is executed through this class/function, the database connection may not be established yet. This is the purpose of this check, so that the consumer (you) does not have to mind about it.
The connection is then stored in the $this->db class member, for future reuse when you call this method again in the course of your script (and yes, this connection will stay open until the script ends -- unless it is closed explicitely beforehand, of course).
For information, this check is slightly inefficient. A simple $this->db->query('SELECT 1') would suffice, without the need to read a table at all.
The second SQL statement below returns an error in phpMyAdmin:
SET #num=2000040;
INSERT INTO artikel( artikel_nr, lieferant_nr, bezeichnung_1, bezeichnung_1 )
SELECT #num := #num +1 AS anum, 70338, f2, f3
FROM import
WHERE id >1
MySQL says:
#1110 - Column 'bezeichnung_1' specified twice
All correct. But when I run the queries in Symfony 1.4 with this function:
// run sql query
// http://erisds.co.uk/symfony/snippet-creating-debugging-complex-sql-queries-in-symfony
// http://stackoverflow.com/questions/5434702/php-quick-refactoring
// param $sql: the query to run
// param $silent: bool if errors should be ignored
// throws: pdo error info if statement failed and $silent=false
// returns: pdo-statement (use for looping over result rows and error messages)
public static function runQuery($sql, $silent=false)
{
$conn = Propel::getConnection();
$pdo_statement = $conn->prepare($sql);
$error = null;
try
{
$pdo_statement->execute();
}
catch (Exception $e)
{
$error = $e->getMessage();
}
if ( !$error )
{
$pdo_error = $pdo_statement->errorInfo();
$error = $pdo_error[2];
}
if ( !$silent && $error ) throw new Exception($error);
return $pdo_statement;
}
no error is thrown. The two SQL statements must be submitted at the same time since they depend on each other. The faulty query is constructed from user input. I need to get that error back, otherwise I can't tell if the database was changed, and I can't tell the user about it.
Do you know why PDO doesn't complain about the invalid statement, and if it can't be made to do so, how to get the success/failure information?
BTW the query does update the database if there are no duplicate columns.
Here's the link to the PDOStatement class: http://www.php.net/manual/en/class.pdostatement.php
This is expected behavior. Since there are two statements and the first one is valid, you have to use nextRowset()
try
{
$pdo_statement->execute();
while ($pdo_statement->nextRowset()) {/* https://bugs.php.net/bug.php?id=61613 */};
}
Source: bugs.php.net/bug.php?id=61613
By default PDOStatement::execute() doesn't throw any exception, it simply returns false on error. You have to set error handling to PDO::ERRMODE_EXCEPTION through db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION).
If you have the option to use mysqli instead of PDO for the multi query, you can use mysqli_multi_query. As error handling is a little complex, here is my function:
/**
* Executes multiple queries at once (separated by semicolons) and discards the results
*
* #param string $sql
* #throws RuntimeException if a query failed
*/
function multi_query($sql) {
$mysqli = new mysqli('host', 'user', 'password', 'database');
//Execute all queries
$index = 1;
if ($mysqli->multi_query($sql)) {
do {
// next_result() fails if store_result() is not called
if ($result = $mysqli->store_result()) {
$result->free();
}
$has_next = $mysqli->more_results();
if (!$has_next)
return; // all queries successfully executed
$index++;
} while ($mysqli->next_result());
}
// At this point, either the multi_query() has returned false - which is
// when the first query failed - or more_results() was true, while next_result()
// returned false - which is when a different query failed.
$error = $mysqli->error;
if (!$error)
$error = $mysqli->errno ? "errno $mysqli->errno" : '(unknown error)';
throw new RuntimeException("mysqli query $index failed: $error");
}