38

I assume that I need to build a native query to truncate a table using Doctine2.

$emptyRsm = new \Doctrine\ORM\Query\ResultSetMapping();
$sql = 'TRUNCATE TABLE Article';
$query = em()->createNativeQuery($sql, $emptyRsm);
$query->execute();

This gives the error

SQLSTATE[HY000]: General error

What do I need to change to my code to make this work?

martin
  • 93,354
  • 25
  • 191
  • 226
murze
  • 4,015
  • 8
  • 43
  • 70

5 Answers5

96

Beware of Truncating Tables

Beware of truncating tables in any RDBMS, especially if you want to use explicit transactions for commit/rollback functionality. Please read the 'My recommendation' of this answer.


DDL statements perform an implicit-commit

Truncate table statements are data definition language (DDL) statements, and as such truncate table statements trigger an implicit COMMIT to the database upon their execution. If you perform a TABLE TRUNCATE then the database is implicitly committed to--even if the TABLE TRUNCATE is within a START TRANSACTION statement--your table will be truncated and a ROLLBACK will not restore it.

Because truncate table statements perform implicit commits, Maxence's answer does not perform as expected (but it's not wrong, because the question was "how to truncate a table"). His answer does not perform as expected because it truncates the table in a try block, and assumes that the table can be restored in the catch block, if something goes wrong. This is an incorrect assumption.


Other user's comments & experiences in this thread

ChrisAelbrecht was unable to get Maxence's solution to work properly because you cannot rollback a truncate table statement, even if the truncate table statement is in an explicit transaction.

user2130519, unfortunately, was downvoted (-1 until I upvoted) for providing the correct answer--although he did so without justifying his answer, which is like doing math without showing your work.


My recommendation DELETE FROM

My recommendation is to use DELETE FROM. In most cases, it will perform as the developer expects. But, DELETE FROM does not come without drawbacks either--you must explicitly reset the auto increment value for the table. To reset the auto increment value for the table, you must use another DDL statement--ALTER TABLE--and, again, don't use ALTER TABLE in your try block. It won't work as expected.

If you want tips on when you should use DELETE FROM vs TRUNCATE see Pros & Cons of TRUNCATE vs DELETE FROM.


If you really must, here's how to truncate

Now, with all that said. If you really want to truncate a table using Doctrine2, use this: (Below is the portion of Maxence's answer that correctly truncates a table)

$cmd = $em->getClassMetadata($className);
$connection = $em->getConnection();
$dbPlatform = $connection->getDatabasePlatform();
$connection->query('SET FOREIGN_KEY_CHECKS=0');
$q = $dbPlatform->getTruncateTableSql($cmd->getTableName());
$connection->executeUpdate($q);
$connection->query('SET FOREIGN_KEY_CHECKS=1');


How to delete a table with rollback/commit functionalty.

But, if you want rollback/commit functionality, you must use DELETE FROM: (Below is a modified version of Maxence's answer.)

$cmd = $em->getClassMetadata($className);
$connection = $em->getConnection();
$connection->beginTransaction();

try {
    $connection->query('SET FOREIGN_KEY_CHECKS=0');
    $connection->query('DELETE FROM '.$cmd->getTableName());
    // Beware of ALTER TABLE here--it's another DDL statement and will cause
    // an implicit commit.
    $connection->query('SET FOREIGN_KEY_CHECKS=1');
    $connection->commit();
} catch (\Exception $e) {
    $connection->rollback();
}

If you need to reset the auto increment value, remember to call ALTER TABLE <tableName> AUTO_INCREMENT = 1.

Martijn
  • 15,791
  • 4
  • 36
  • 68
cmt
  • 1,475
  • 9
  • 11
  • 8
    just because i had to google it: $con->exec('ALTER TABLE ' . $cmd->getTableName() . ' AUTO_INCREMENT = 1;'); – MonocroM Aug 27 '14 at 08:56
  • 1
    and don't forget to add `$em->clear();` after alter command or you have a chance to insert old record to database – vvolkov Oct 14 '15 at 08:31
  • 1
    This has a real "Don't try this at home kids" feel to it! Well thought out answer though. I assume if I do alter table to reset the auto increment number, it breaks the ability to rollback then based on what you've said? – Scott Flack Feb 26 '16 at 06:01
  • 1
    For those who use PostgreSQL - generally DDL command can run in transactions and as stated in Documentation `TRUNCATE is transaction-safe with respect to the data in the tables: the truncation will be safely rolled back if the surrounding transaction does not commit.` – Jan Mares Mar 11 '16 at 16:16
40

Here is the code I'm using:

$cmd = $em->getClassMetadata($className);
$connection = $em->getConnection();
$dbPlatform = $connection->getDatabasePlatform();
$connection->beginTransaction();
try {
    $connection->query('SET FOREIGN_KEY_CHECKS=0');
    $q = $dbPlatform->getTruncateTableSql($cmd->getTableName());
    $connection->executeUpdate($q);
    $connection->query('SET FOREIGN_KEY_CHECKS=1');
    $connection->commit();
}
catch (\Exception $e) {
    $connection->rollback();
}
Maxence
  • 12,868
  • 5
  • 57
  • 69
  • Just tested this code with Symfony 2.1.0 release and works fine! Thanks! – aletzo Sep 10 '12 at 10:22
  • 1
    I was just about to start writing engine specific code to truncate a table, thanks for your useful contribution :) – Lee Davis Nov 08 '12 at 11:18
  • 5
    simpler version for mysql `$em->getConnection()->query('START TRANSACTION;SET FOREIGN_KEY_CHECKS=0; TRUNCATE table1; TRUNCATE table2;SET FOREIGN_KEY_CHECKS=1; COMMIT;')` – E Ciotti Apr 19 '13 at 14:15
  • The great thing using DBAL is that the "AL"part of the name means "Abstraction Layer". If you directly do it "for mysql", you can use PDO and forget DBAL at all. Setting the "TRUNCATE table" inside the string might break compatibility with other engines. – Xavi Montero Jan 12 '22 at 10:20
11

Or you could just try this:

$this->getEm()->createQuery('DELETE AcmeBundle:Post p')->execute();

If you have relations you should be careful to handle the linked entities.

ksadowski
  • 161
  • 9
piers.warmers
  • 333
  • 2
  • 6
1

This is example truncating method from trait in unit tests.

/**
 * Cleanup any needed table abroad TRUNCATE SQL function
 *
 * @param string $className (example: App\Entity\User)
 * @param EntityManager $em
 * @return bool
 */
private function truncateTable (string $className, EntityManager $em): bool {
    $cmd = $em->getClassMetadata($className);
    $connection = $em->getConnection();
    $connection->beginTransaction();

    try {
        $connection->query('SET FOREIGN_KEY_CHECKS=0');
        $connection->query('TRUNCATE TABLE '.$cmd->getTableName());
        $connection->query('SET FOREIGN_KEY_CHECKS=1');
        $connection->commit();
        $em->flush();
    } catch (\Exception $e) {
        try {
            fwrite(STDERR, print_r('Can\'t truncate table ' . $cmd->getTableName() . '. Reason: ' . $e->getMessage(), TRUE));
            $connection->rollback();
            return false;
        } catch (ConnectionException $connectionException) {
            fwrite(STDERR, print_r('Can\'t rollback truncating table ' . $cmd->getTableName() . '. Reason: ' . $connectionException->getMessage(), TRUE));
            return false;
        }
    }
    return true;
}

Please note, that if you do not use $em->flush(), you have a risk to have a problem with next query to doctrine.

Also you must understand, that if you use this method in a controller, you must change the lines fwrite(STDERR, print_r(... to something your logger service can use.

Philipp Maurer
  • 2,480
  • 6
  • 18
  • 25
1

This is how you can delete all entites from a entity reposiotry in symfony using doctrine (not ignoring foreign key checks). The functions returns the count of the deleted entites.

/**
 * @return int
 */
public function deleteAll(): int
{
    $qb = $this->createQueryBuilder('t');

    $qb->delete();

    return $qb->getQuery()->getSingleScalarResult() ?? 0;
}
Sebastian Viereck
  • 5,455
  • 53
  • 53