1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
|
<?php
declare(strict_types=1);
namespace Doctrine\ORM\Query\Exec;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Connections\PrimaryReadReplicaConnection;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\Query\AST;
use Doctrine\ORM\Query\AST\DeleteStatement;
use Doctrine\ORM\Query\SqlWalker;
use Doctrine\ORM\Utility\PersisterHelper;
use Throwable;
use function array_merge;
use function array_reverse;
use function implode;
/**
* Executes the SQL statements for bulk DQL DELETE statements on classes in
* Class Table Inheritance (JOINED).
*
* @link http://www.doctrine-project.org
*/
class MultiTableDeleteExecutor extends AbstractSqlExecutor
{
/** @var string */
private $_createTempTableSql;
/** @var string */
private $_dropTempTableSql;
/** @var string */
private $_insertSql;
/**
* Initializes a new <tt>MultiTableDeleteExecutor</tt>.
*
* Internal note: Any SQL construction and preparation takes place in the constructor for
* best performance. With a query cache the executor will be cached.
*
* @param DeleteStatement $AST The root AST node of the DQL query.
* @param SqlWalker $sqlWalker The walker used for SQL generation from the AST.
*/
public function __construct(AST\Node $AST, $sqlWalker)
{
$em = $sqlWalker->getEntityManager();
$conn = $em->getConnection();
$platform = $conn->getDatabasePlatform();
$quoteStrategy = $em->getConfiguration()->getQuoteStrategy();
if ($conn instanceof PrimaryReadReplicaConnection) {
$conn->ensureConnectedToPrimary();
}
$primaryClass = $em->getClassMetadata($AST->deleteClause->abstractSchemaName);
$primaryDqlAlias = $AST->deleteClause->aliasIdentificationVariable;
$rootClass = $em->getClassMetadata($primaryClass->rootEntityName);
$tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName());
$idColumnNames = $rootClass->getIdentifierColumnNames();
$idColumnList = implode(', ', $idColumnNames);
// 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
$sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $primaryDqlAlias);
$this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')'
. ' SELECT t0.' . implode(', t0.', $idColumnNames);
$rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $primaryDqlAlias);
$fromClause = new AST\FromClause([new AST\IdentificationVariableDeclaration($rangeDecl, null, [])]);
$this->_insertSql .= $sqlWalker->walkFromClause($fromClause);
// Append WHERE clause, if there is one.
if ($AST->whereClause) {
$this->_insertSql .= $sqlWalker->walkWhereClause($AST->whereClause);
}
// 2. Create ID subselect statement used in DELETE ... WHERE ... IN (subselect)
$idSubselect = 'SELECT ' . $idColumnList . ' FROM ' . $tempTable;
// 3. Create and store DELETE statements
$classNames = array_merge($primaryClass->parentClasses, [$primaryClass->name], $primaryClass->subClasses);
foreach (array_reverse($classNames) as $className) {
$tableName = $quoteStrategy->getTableName($em->getClassMetadata($className), $platform);
$this->_sqlStatements[] = 'DELETE FROM ' . $tableName
. ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')';
}
// 4. Store DDL for temporary identifier table.
$columnDefinitions = [];
foreach ($idColumnNames as $idColumnName) {
$columnDefinitions[$idColumnName] = [
'notnull' => true,
'type' => Type::getType(PersisterHelper::getTypeOfColumn($idColumnName, $rootClass, $em)),
];
}
$this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
. $platform->getColumnDeclarationListSQL($columnDefinitions) . ', PRIMARY KEY(' . implode(',', $idColumnNames) . '))';
$this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
}
/**
* {@inheritDoc}
*
* @return int
*/
public function execute(Connection $conn, array $params, array $types)
{
// Create temporary id table
$conn->executeStatement($this->_createTempTableSql);
try {
// Insert identifiers
$numDeleted = $conn->executeStatement($this->_insertSql, $params, $types);
// Execute DELETE statements
foreach ($this->_sqlStatements as $sql) {
$conn->executeStatement($sql);
}
} catch (Throwable $exception) {
// FAILURE! Drop temporary table to avoid possible collisions
$conn->executeStatement($this->_dropTempTableSql);
// Re-throw exception
throw $exception;
}
// Drop temporary table
$conn->executeStatement($this->_dropTempTableSql);
return $numDeleted;
}
}
|