Home » Php » php – Is there a way to throw exceptions when any statement fails in PDO::exec()?

php – Is there a way to throw exceptions when any statement fails in PDO::exec()?

Posted by: admin July 12, 2020 Leave a comment

Questions:

PDO::exec() allows (at least with some drivers such as mysqlnd) to execute several statements at a time.

This works fine, and when I pass several queries to PDO::exec() they all get executed:

$pdo->exec('DROP TABLE a; DROP TABLE b;');

My PDO instance is configured to throw exceptions:

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

If the first query fails, it throws an exception as expected:

$pdo->exec('DROP TABLE does_not_exist; DROP TABLE ok;'); // PDOException

But when any subsequent query fails, it silently ignores this fact and you don’t seem to have a way to know it:

$pdo->exec('DROP TABLE ok; DROP TABLE does_not_exist;'); // no exception
var_export($pdo->errorInfo()); // array (0 => '00000', 1 => NULL, 2 => NULL)

Is there any way to configure PDO so that exec() throws an exception if any of the statements fail?

Please note that I don’t currently have the obviously better option to run each query in its own exec() call, as I’m writing a tool that reads SQL dump files.

How to&Answers:

Interesting question… I believe (correct me if I’m wrong) that this “multiple” exec will call each exec after each… so once you get exception it is returned and the execution of your queries is stopped.

used example:
(dbname) test contains 2 tables ‘a’ and ‘b’

$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

echo "BEFORE:<br/>";
foreach($db->query("show tables;") as $row) echo $row[0] . "<br />";

$a = $db->exec("drop table b; drop table c");

echo "AFTER:<br/>";
foreach($db->query("show tables;") as $row) echo $row[0] . "<br />";

here i get result:

BEFORE:
a
b
AFTER:
a
1              <--- ^.^

which is somehow stupid. but probably the correct solution would be to use a PDO transaction. You should be able to revert changes if some of your code fails. Basically if you start transaction you will turn off autocommit after each query (WITH EXCEPTION OF 3 queries mentioned at the end!!!). Revert or commit will turn autocommit on again.

Try to instead of $db->exec(“q1; q2; q3”)… something like this:

try {
    $db->beginTransaction();
    $db->exec("drop table b;");           // -- note at the end of post!
    $db->exec("drop table c;");
    $db->commit();
} catch (PDOException $e) {
    print_r($e);
    $db->rollBack();
}

Basically this aproach works. HOWEVER!

be aware that you can not use TRUNCATE TABLE as this statement will trigger a commit just like CREATE TABLE or DROP TABLE.

So if you are handling queries like DROP TABLE etc… your correct solution in this particular case is to use this query instead of simple drop:

SQL: DROP TABLE IF EXISTS `tablename`;

this statement won’t trigger exception 😉

Hope it helps ^.^

Answer:

As mentioned in bug #61613, it is possible to get an exception if any of the statements fails.

The solution is to use emulated prepares (on by default), and PDOStatement::nextRowset():

$pdo = new PDO(...);

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

// on by default, not necessary unless you need to override a previous configuration
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

try {
    $statement = $pdo->query('DELETE FROM unknown_a; DELETE FROM unknown_b;');

    // loop through all the statements
    while ($statement->nextRowset());
} catch (PDOException $e) {
    echo $e->getMessage(), PHP_EOL;
}

If the first statement fails, query() will throw an exception.

If the first statement succeeds and the second statement fails, query() will work fine, and nextRowset() will throw an exception.

Caveat: no further statements are executed after a failed statement. Say you have a SQL string containing 4 statements, and the third one fails:

  • 1st statement: query() will succeed
  • 2nd statement: nextRowset() will succeed and return true
  • 3rd statement: nextRowset() will fail with an exception
  • 4th statement: nextRowset() would return false; the statement is not executed.

If you’re using the code above, it stops on first exception anyway.