Rajout de doctrine/orm
[zf2.biz/galerie.git] / vendor / doctrine / orm / lib / Doctrine / ORM / Query / Exec / MultiTableUpdateExecutor.php
1 <?php
2 /*
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.
14  *
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>.
18  */
19
20 namespace Doctrine\ORM\Query\Exec;
21
22 use Doctrine\DBAL\Connection;
23 use Doctrine\DBAL\Types\Type;
24
25 use Doctrine\ORM\Query\ParameterTypeInferer;
26 use Doctrine\ORM\Query\AST;
27
28 /**
29  * Executes the SQL statements for bulk DQL UPDATE statements on classes in
30  * Class Table Inheritance (JOINED).
31  *
32  * @author Roman Borschel <roman@code-factory.org>
33  * @since 2.0
34  */
35 class MultiTableUpdateExecutor extends AbstractSqlExecutor
36 {
37     private $_createTempTableSql;
38     private $_dropTempTableSql;
39     private $_insertSql;
40     private $_sqlParameters = array();
41     private $_numParametersInUpdateClause = 0;
42
43     /**
44      * Initializes a new <tt>MultiTableUpdateExecutor</tt>.
45      *
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.
50      */
51     public function __construct(AST\Node $AST, $sqlWalker)
52     {
53         $em             = $sqlWalker->getEntityManager();
54         $conn           = $em->getConnection();
55         $platform       = $conn->getDatabasePlatform();
56         $quoteStrategy  = $em->getConfiguration()->getQuoteStrategy();
57
58         $updateClause   = $AST->updateClause;
59         $primaryClass   = $sqlWalker->getEntityManager()->getClassMetadata($updateClause->abstractSchemaName);
60         $rootClass      = $em->getClassMetadata($primaryClass->rootEntityName);
61
62         $updateItems    = $updateClause->updateItems;
63
64         $tempTable      = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName());
65         $idColumnNames  = $rootClass->getIdentifierColumnNames();
66         $idColumnList   = implode(', ', $idColumnNames);
67
68         // 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause()
69         $sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 't0', $updateClause->aliasIdentificationVariable);
70
71         $this->_insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnList . ')'
72                 . ' SELECT t0.' . implode(', t0.', $idColumnNames);
73
74         $rangeDecl = new AST\RangeVariableDeclaration($primaryClass->name, $updateClause->aliasIdentificationVariable);
75         $fromClause = new AST\FromClause(array(new AST\IdentificationVariableDeclaration($rangeDecl, null, array())));
76
77         $this->_insertSql .= $sqlWalker->walkFromClause($fromClause);
78
79         // 2. Create ID subselect statement used in UPDATE ... WHERE ... IN (subselect)
80         $idSubselect = 'SELECT ' . $idColumnList . ' FROM ' . $tempTable;
81
82         // 3. Create and store UPDATE statements
83         $classNames = array_merge($primaryClass->parentClasses, array($primaryClass->name), $primaryClass->subClasses);
84         $i = -1;
85
86         foreach (array_reverse($classNames) as $className) {
87             $affected = false;
88             $class = $em->getClassMetadata($className);
89             $updateSql = 'UPDATE ' . $quoteStrategy->getTableName($class, $platform) . ' SET ';
90
91             foreach ($updateItems as $updateItem) {
92                 $field = $updateItem->pathExpression->field;
93
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;
97
98                     if ( ! $affected) {
99                         $affected = true;
100                         ++$i;
101                     } else {
102                         $updateSql .= ', ';
103                     }
104
105                     $updateSql .= $sqlWalker->walkUpdateItem($updateItem);
106
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);
112
113                         $value = $sqlWalker->getQuery()->processParameterValue($parameter->getValue());
114                         $type  = ($parameter->getValue() === $value)
115                             ? $parameter->getType()
116                             : ParameterTypeInferer::inferType($value);
117
118                         $this->_sqlParameters[$i]['parameters'][] = $value;
119                         $this->_sqlParameters[$i]['types'][] = $type;
120
121                         ++$this->_numParametersInUpdateClause;
122                     }
123                 }
124             }
125
126             if ($affected) {
127                 $this->_sqlStatements[$i] = $updateSql . ' WHERE (' . $idColumnList . ') IN (' . $idSubselect . ')';
128             }
129         }
130
131         // Append WHERE clause to insertSql, if there is one.
132         if ($AST->whereClause) {
133             $this->_insertSql .= $sqlWalker->walkWhereClause($AST->whereClause);
134         }
135
136         // 4. Store DDL for temporary identifier table.
137         $columnDefinitions = array();
138
139         foreach ($idColumnNames as $idColumnName) {
140             $columnDefinitions[$idColumnName] = array(
141                 'notnull' => true,
142                 'type' => Type::getType($rootClass->getTypeOfColumn($idColumnName))
143             );
144         }
145
146         $this->_createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' ('
147                 . $platform->getColumnDeclarationListSQL($columnDefinitions) . ')';
148
149         $this->_dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable);
150     }
151
152     /**
153      * {@inheritDoc}
154      */
155     public function execute(Connection $conn, array $params, array $types)
156     {
157         $numUpdated = 0;
158
159         // Create temporary id table
160         $conn->executeUpdate($this->_createTempTableSql);
161
162         try {
163             // Insert identifiers. Parameters from the update clause are cut off.
164             $numUpdated = $conn->executeUpdate(
165                 $this->_insertSql,
166                 array_slice($params, $this->_numParametersInUpdateClause),
167                 array_slice($types, $this->_numParametersInUpdateClause)
168             );
169
170             // Execute UPDATE statements
171             for ($i=0, $count=count($this->_sqlStatements); $i<$count; ++$i) {
172                 $parameters = array();
173                 $types      = array();
174
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();
178                 }
179
180                 $conn->executeUpdate($this->_sqlStatements[$i], $parameters, $types);
181             }
182         } catch (\Exception $exception) {
183             // FAILURE! Drop temporary table to avoid possible collisions
184             $conn->executeUpdate($this->_dropTempTableSql);
185
186             // Re-throw exception
187             throw $exception;
188         }
189
190         // Drop temporary table
191         $conn->executeUpdate($this->_dropTempTableSql);
192
193         return $numUpdated;
194     }
195 }