How to turn this SQL Query into PDO Transaction without Errors - php

I have this SQL Query:
BEGIN;
INSERT INTO users (username, password)
VALUES('alpha', 'omega');
INSERT INTO profiles (userid, cv, website)
VALUES(LAST_INSERT_ID(),'some cv things', 'www.domain.com');
COMMIT;
And I want to use PDO instead of the MYSQL Transcation so I can pull the catched error Because I don't get it from the MYSQL Transaction, What I've tried was
$dbh->beginTransaction();
try {
$stmt = $dbh->prepare("
INSERT INTO users (username, password)
VALUES('alpha', 'omega');
INSERT INTO profiles (userid, cv, website)
VALUES(LAST_INSERT_ID(),'some cv things', 'www.domain.com');
");
$stmt->execute();
$dbh->commit();
} catch (PDOException $e) {
$dbh->rollback();
throw $e;
}
But I kept getting this Error
Uncaught PDOException: There is no active transaction . . . PDO->rollBack() #1 {main} thrown
The reocrds were INSERTed into the Table, But the error kept being shown too.
I've tried using $dbh->setAttribute(PDO::ATTR_AUTOCOMMIT, FALSE); but still got the Error message.
Then tried to remove try-catch and kept the Query with the PDO Transactions and got this Error message
Uncaught PDOException: There is no active transaction . . . PDO->commit() #1 {main} thrown

You're throwing the exception in your error handler:
catch (PDOException $e) {
$dbh->rollback();
throw $e; // <-- right here
}
That will cause the exception to keep going until it's handled, and if it's not handled it will throw a fatal error:
When an exception is thrown, code following the statement will not be executed, and PHP will attempt to find the first matching catch block. If an exception is not caught, a PHP Fatal Error will be issued with an "Uncaught Exception ..." message, unless a handler has been defined with set_exception_handler().
If your exception handler is completely handling the exception you should remove that line.

you're not passing any params do it like this:
try {
$stmt = $dbh->exec('
INSERT INTO `users` (`username`, `password`)
VALUES("alpha", "omega");
');
$stmt_2 = $dbh->exec('
INSERT INTO `profiles` (`userid`, `cv`, `website`)
VALUES(LAST_INSERT_ID(), "some cv things", "www.domain.com");
');
} catch (PDOException $e) {
$dbh->rollback();
throw $e;
}
this will execute the query - you don't need to prepare because you're already escaping your strings so a simple $dbh->exec will do the trick :)

Related

Retrieve error from SQL Server in PHP

I'm trying to show the users (in PHP) when some block of SQL actions commit failed for some reason. But it look likes PHP DBO never gets the error. This is a simplification of the actual code.
$conn = new PDO("dblib:host=$host; dbname=$db", $user, $pass);
$sql = "
BEGIN TRAN
INSERT INTO table (field) VALUES (1)
INSERT INTO table (field) VALUES (2)
INSERT INTO table (field) VALUES ('this will throw an error')
COMMIT
IF ##ERROR <> 0 BEGIN
ROLLBACK
RAISERROR('Nops.', 16, 1)
END";
try {
$rs = $conn->query($sql);
} catch (PDOException $e) {
echo 'It never gets here';
}
How do I get that this transaction was not commited?

Can I use rollBack() before commit()?

Here is my script:
try{
$db_conn->beginTransaction();
$stm1 = $db_conn->prepare("UPDATE table1 SET col = 'updated' WHERE id = ?");
$stm1->execute(array($value));
$done = $stm->rowCount();
if ($done){
try {
$stm2 = $db_conn->prepare("INSERT into table2 (col) VALUES (?)");
$stm2->execute(array($id));
} catch(PDOException $e){
if ((int) $e->getCode() === 23000) { // row is duplicate
$stm3 = $db_conn->prepare("DELETE FROM table2 WHERE col = ?");
$stm3->execute(array($id));
} else {
$db_conn->rollBack(); -- this
}
}
} else {
$error = true;
}
$db_conn->commit();
}
catch(PDOException $e){
$db_conn->rollBack();
}
As you see there is a rollBack() before commit() (where I've commented by -- this). Well is what I'm doing correct? Or that rollBack() is useless?
Note: that DELETE statement acts as an undo. Suppose you give a vote to a post and you want to take it back. So that DELETE statement remove it if you send a vote twice.
You dont need to make it quite so complicated.
You can run the 2 queries within a single try/catch as any of the queries that has an isse will throw an exception, and then you can do a single rollback.
If the first query fails, the database will not be changed, the rollback will just close the transaction. If the second query fails the rollback will UNDO the first query i.e. the UPDATE you did previously.
If both queries complete OK, the commit will apply the changes to the database.
try{
$db_conn->beginTransaction();
$stm1 = $db_conn->prepare("UPDATE table1 SET col = 'updated' WHERE id = ?");
$stm1->execute(array($value));
$stm2 = $db_conn->prepare("INSERT into table2 (col) VALUES (?)");
$stm2->execute(array($id));
$db_conn->commit();
}
catch(PDOException $e){
$db_conn->rollBack();
}
ADDITIONAL NOTES
I see what you think you were trying to do. BUT!
If the INSERT fails with a 23000 error code, then the INSERT will not have been done. Your unnecessary attempt to delete the failed INSERT will actually DELETE the row that was there originally i.e. the original vote that should not be deleted!
Your code would work fine. You can get rid of your inner rollback if you would rethrow your error (that would be the "standard way"):
try {
...
if ($done){
try {
...
} catch(PDOException $e){
if ((int) $e->getCode() === 23000) { // row is duplicate
...
} else {
throw $e;
}
}
...
} else {
$error = true;
}
$db_conn->commit();
}
catch(PDOException $e){
$db_conn->rollBack();
}
In this case, your final rollback will handle all exceptions that should be rolled back, while handling the one exception 23000 yourself without rollback.
And it would work fine in your code too. Technically, you can in fact combine as many commits and rollbacks as you want without resulting in an error - if you commit after a rollback, it will just commit nothing, because it rolled back already. If you use rollback without start transaction (in autocommit mode), it will just do nothing. It is just a little harder to maintain the code and to see the strcuture, that's why you usually would use the "standard way" above.
There is just one important thing to consider: transactions in mysql are not nested. If you use start transaction, it will automatically do a commit before that. So e.g.
start transaction;
delete from very_important_table;
start transaction; -- will do an autocommit
rollback;
rollback; -- will have no effect;
commit; -- will have no effect;
rollback; -- will have no effect;
will not rollback your very_import_table, because the 2nd start transaction already committed it.

PDO Last insert ID

I am using $insertedId = $pdo_conn->lastInsertId(); to get the last inserted ID after an insert query then i run another insert query:
foreach ($records as $emails_to) {
$stmt = $pdo_conn->prepare("INSERT into emails_to (email_seq, email) values (:email_seq, :email) ");
$stmt->execute(array(':email_seq' => $InsertedId, ':email' => $emails_to["email"]));
}
but it doesn't seem to recognise the last insert ID, i get this error:
Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'email_seq' cannot be null'
what have i done wrong?
$insertedId and $InsertedId are not the same. Variable names are case-sensitive.
Your $insertedID doesn't match $InsertedID - case issue
edit; darn, beaten to the post
Beware of lastInsertId() when working with transactions in mysql. The following code returns 0 instead of the insert id.
This is an example
<?php
try {
$dbh = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');
$stmt = $dbh->prepare("INSERT INTO test (name, email) VALUES(?,?)");
try {
$dbh->beginTransaction();
$stmt->execute( array('user', 'user#example.com'));
$dbh->commit();
print $dbh->lastInsertId();
}
catch(PDOException $e) {
$dbh->rollback();
print "Error!: " . $e->getMessage() . "</br>";
}
}
catch( PDOException $e ) {
print "Error!: " . $e->getMessage() . "</br>";
}
?>
When no exception is thrown, lastInsertId returns 0. However, if lastInsertId is called before calling commit, the right id is returned.
for more informations visit -> PHP

SQL query fails but the transaction rollback doesn't work

The actual column names are account and passwd so the second query will failed, but the first query still inserted into database.
$sql1 = "INSERT INTO users (account, passwd) VALUES ('account+1', 'password+1')";
$sql2 = "INSERT INTO users (account, password) VALUES ('account+2', 'password+2')";
try {
$db->beginTransaction();
$db->query($sql1);
$db->query($sql2);
$db->commit();
} catch (Exception $e) {
$db->rollback();
die($e->getMessage());
}
The transaction started successfully with no errors, the problem is that it doesn't rollback, always rows are inserted regardless of failing queries.
MySQL version 5.5.25 and table type is InnoDB.
Did you turn on exceptions? By default pdo doesn't throw them and silently ignores errors.
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$sql1 = "INSERT INTO user (account, passwd) VALUES ('account+1', 'password+1')";
$sql2 = "INSERT INTO users (account, password) VALUES ('account+2', 'password+2')";
try {
$db->beginTransaction();
$db->query($sql1);
$db->query($sql2);
$db->commit();
} catch (Exception $e) {
$db->rollback();
die($e->getMessage());
}
$mysql refence is wrong, you have to use, $db->rollback();

Trace type of error mySql PDO php

I would like to trace the type of error that occurred while executing a query using PDO prepare and execute.
For example, if I had to insert email, Name, DOB, with the email requiring to be unique, instead of writing a special query to check if Email is already taken, I would like to trace the error that occurred and know the same.
You could use a try/catch block around the insert query and handle the double email in the exception handler.
$sql = "INSERT INTO `mystuff` (email, name ) VALUES (:email, :name)";
try {
$sth = $dbh->prepare($sql);
$sth->execute(array(':email' => $email, ':name' => $name));
}
catch (Exception $e) {
echo 'email already taken';
}
Of course you must configure PDO that it throws exceptions on errors.
First of all you will configure your database to put a UNIQUE KEY
ALTER TABLE yourtable ADD CONSTRAINT uniqukey_for_mail UNIQUE(email);
Then you will use PDO with ERRMODE_EXCEPTION
$bddConnect = new PDO('dsn',$user,$password);
$bddConnect->setAttribute(PDO::ATTR_ERRMOD,PDO::ERRMOD_EXCEPTION);
Then when you will update or insert datas, you will just have to wrap your query with a try/catch, and check that your error is a constraint violation
try{
$bddConnect->exec("UPDATE yourtable SET email='already in table EMAIL' WHERE
id='other id than the one that has the email'");
}catch(PDOException $e){
if(strpos('constraint violation',$e->getMessage())!==false){
echo "already taken";
}
}

Categories