3 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15 * This software consists of voluntary contributions made by many individuals
16 * and is licensed under the MIT license. For more information, see
17 * <http://www.doctrine-project.org>.
20 namespace Doctrine\ORM\Query\Exec;
22 use Doctrine\DBAL\Connection;
23 use Doctrine\DBAL\Types\Type;
25 use Doctrine\ORM\Query\ParameterTypeInferer;
26 use Doctrine\ORM\Query\AST;
29 * Executes the SQL statements for bulk DQL UPDATE statements on classes in
30 * Class Table Inheritance (JOINED).
32 * @author Roman Borschel <roman@code-factory.org>
35 class MultiTableUpdateExecutor extends AbstractSqlExecutor
37 private $_createTempTableSql;
38 private $_dropTempTableSql;
40 private $_sqlParameters = array();
41 private $_numParametersInUpdateClause = 0;
44 * Initializes a new <tt>MultiTableUpdateExecutor</tt>.
46 * @param Node $AST The root AST node of the DQL query.
47 * @param SqlWalker $sqlWalker The walker used for SQL generation from the AST.
48 * @internal Any SQL construction and preparation takes place in the constructor for
49 * best performance. With a query cache the executor will be cached.
51 public function __construct(AST\Node $AST, $sqlWalker)
53 $em = $sqlWalker->getEntityManager();
54 $conn = $em->getConnection();
55 $platform = $conn->getDatabasePlatform();
56 $quoteStrategy = $em->getConfiguration()->getQuoteStrategy();
58 $updateClause = $AST->updateClause;
59 $primaryClass = $sqlWalker->getEntityManager()->getClassMetadata($updateClause->abstractSchemaName);
60 $rootClass = $em->getClassMetadata($primaryClass->rootEntityName);
62 $updateItems = $updateClause->updateItems;
64 $tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName());
65 $idColumnNames = $rootClass->getIdentifierColumnNames();
66 $idColumnList = implode(', ', $idColumnNames);
68 // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
69 $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $updateClause->aliasIdentificationVariable);
71 $this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')'
72 . ' SELECT t0.' . implode(', t0.', $idColumnNames);
74 $rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $updateClause->aliasIdentificationVariable);
75 $fromClause = new AST\FromClause(array(new AST\IdentificationVariableDeclaration($rangeDecl, null, array())));
77 $this->_insertSql .= $sqlWalker->walkFromClause($fromClause);
79 // 2. Create ID subselect statement used in UPDATE ... WHERE ... IN (subselect)
80 $idSubselect = 'SELECT ' . $idColumnList . ' FROM ' . $tempTable;
82 // 3. Create and store UPDATE statements
83 $classNames = array_merge($primaryClass->parentClasses, array($primaryClass->name), $primaryClass->subClasses);
86 foreach (array_reverse($classNames) as $className) {
88 $class = $em->getClassMetadata($className);
89 $updateSql = 'UPDATE ' . $quoteStrategy->getTableName($class, $platform) . ' SET ';
91 foreach ($updateItems as $updateItem) {
92 $field = $updateItem->pathExpression->field;
94 if (isset($class->fieldMappings[$field]) && ! isset($class->fieldMappings[$field]['inherited']) ||
95 isset($class->associationMappings[$field]) && ! isset($class->associationMappings[$field]['inherited'])) {
96 $newValue = $updateItem->newValue;
105 $updateSql .= $sqlWalker->walkUpdateItem($updateItem);
107 //FIXME: parameters can be more deeply nested. traverse the tree.
108 //FIXME (URGENT): With query cache the parameter is out of date. Move to execute() stage.
109 if ($newValue instanceof AST\InputParameter) {
110 $parameterName = $newValue->name;
111 $parameter = $sqlWalker->getQuery()->getParameter($parameterName);
113 $value = $sqlWalker->getQuery()->processParameterValue($parameter->getValue());
114 $type = ($parameter->getValue() === $value)
115 ? $parameter->getType()
116 : ParameterTypeInferer::inferType($value);
118 $this->_sqlParameters[$i]['parameters'][] = $value;
119 $this->_sqlParameters[$i]['types'][] = $type;
121 ++$this->_numParametersInUpdateClause;
127 $this->_sqlStatements[$i] = $updateSql . ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')';
131 // Append WHERE clause to insertSql, if there is one.
132 if ($AST->whereClause) {
133 $this->_insertSql .= $sqlWalker->walkWhereClause($AST->whereClause);
136 // 4. Store DDL for temporary identifier table.
137 $columnDefinitions = array();
139 foreach ($idColumnNames as $idColumnName) {
140 $columnDefinitions[$idColumnName] = array(
142 'type' => Type::getType($rootClass->getTypeOfColumn($idColumnName))
146 $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
147 . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
149 $this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
155 public function execute(Connection $conn, array $params, array $types)
159 // Create temporary id table
160 $conn->executeUpdate($this->_createTempTableSql);
163 // Insert identifiers. Parameters from the update clause are cut off.
164 $numUpdated = $conn->executeUpdate(
166 array_slice($params, $this->_numParametersInUpdateClause),
167 array_slice($types, $this->_numParametersInUpdateClause)
170 // Execute UPDATE statements
171 for ($i=0, $count=count($this->_sqlStatements); $i<$count; ++$i) {
172 $parameters = array();
175 if (isset($this->_sqlParameters[$i])) {
176 $parameters = isset($this->_sqlParameters[$i]['parameters']) ? $this->_sqlParameters[$i]['parameters'] : array();
177 $types = isset($this->_sqlParameters[$i]['types']) ? $this->_sqlParameters[$i]['types'] : array();
180 $conn->executeUpdate($this->_sqlStatements[$i], $parameters, $types);
182 } catch (\Exception $exception) {
183 // FAILURE! Drop temporary table to avoid possible collisions
184 $conn->executeUpdate($this->_dropTempTableSql);
186 // Re-throw exception
190 // Drop temporary table
191 $conn->executeUpdate($this->_dropTempTableSql);