1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
namespace Doctrine\ORM\Query\Exec; |
6
|
|
|
|
7
|
|
|
use Doctrine\DBAL\Connection; |
8
|
|
|
use Doctrine\ORM\Mapping\ColumnMetadata; |
9
|
|
|
use Doctrine\ORM\Query\AST; |
10
|
|
|
use Doctrine\ORM\Query\ParameterTypeInferer; |
11
|
|
|
use Doctrine\ORM\Utility\PersisterHelper; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Executes the SQL statements for bulk DQL UPDATE statements on classes in |
15
|
|
|
* Class Table Inheritance (JOINED). |
16
|
|
|
* |
17
|
|
|
* @author Roman Borschel <[email protected]> |
18
|
|
|
* @since 2.0 |
19
|
|
|
*/ |
20
|
|
|
class MultiTableUpdateExecutor extends AbstractSqlExecutor |
21
|
|
|
{ |
22
|
|
|
/** |
23
|
|
|
* @var string |
24
|
|
|
*/ |
25
|
|
|
private $createTempTableSql; |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* @var string |
29
|
|
|
*/ |
30
|
|
|
private $dropTempTableSql; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @var string |
34
|
|
|
*/ |
35
|
|
|
private $insertSql; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var array |
39
|
|
|
*/ |
40
|
|
|
private $sqlParameters = []; |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @var int |
44
|
|
|
*/ |
45
|
|
|
private $numParametersInUpdateClause = 0; |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Initializes a new <tt>MultiTableUpdateExecutor</tt>. |
49
|
|
|
* |
50
|
|
|
* Internal note: Any SQL construction and preparation takes place in the constructor for |
51
|
|
|
* best performance. With a query cache the executor will be cached. |
52
|
|
|
* |
53
|
|
|
* @param \Doctrine\ORM\Query\AST\Node $AST The root AST node of the DQL query. |
54
|
|
|
* @param \Doctrine\ORM\Query\SqlWalker $sqlWalker The walker used for SQL generation from the AST. |
55
|
|
|
*/ |
56
|
|
|
public function __construct(AST\Node $AST, $sqlWalker) |
57
|
|
|
{ |
58
|
|
|
$em = $sqlWalker->getEntityManager(); |
59
|
|
|
$conn = $em->getConnection(); |
60
|
|
|
$platform = $conn->getDatabasePlatform(); |
61
|
|
|
|
62
|
|
|
$updateClause = $AST->updateClause; |
|
|
|
|
63
|
|
|
$primaryClass = $sqlWalker->getEntityManager()->getClassMetadata($updateClause->abstractSchemaName); |
64
|
|
|
$rootClass = $em->getClassMetadata($primaryClass->getRootClassName()); |
|
|
|
|
65
|
|
|
|
66
|
|
|
$updateItems = $updateClause->updateItems; |
67
|
|
|
|
68
|
|
|
$tempTable = $platform->getTemporaryTableName($rootClass->getTemporaryIdTableName()); |
|
|
|
|
69
|
|
|
$idColumns = $rootClass->getIdentifierColumns($em); |
|
|
|
|
70
|
|
|
$idColumnNameList = implode(', ', array_keys($idColumns)); |
71
|
4 |
|
|
72
|
|
|
// 1. Create an INSERT INTO temptable ... SELECT identifiers WHERE $AST->getWhereClause() |
73
|
4 |
|
$sqlWalker->setSQLTableAlias($primaryClass->getTableName(), 'i0', $updateClause->aliasIdentificationVariable); |
|
|
|
|
74
|
4 |
|
|
75
|
4 |
|
$this->insertSql = 'INSERT INTO ' . $tempTable . ' (' . $idColumnNameList . ')' |
76
|
4 |
|
. ' SELECT i0.' . implode(', i0.', array_keys($idColumns)); |
77
|
|
|
|
78
|
4 |
|
$rangeDecl = new AST\RangeVariableDeclaration($primaryClass->getClassName(), $updateClause->aliasIdentificationVariable); |
|
|
|
|
79
|
4 |
|
$fromClause = new AST\FromClause([new AST\IdentificationVariableDeclaration($rangeDecl, null, [])]); |
80
|
4 |
|
|
81
|
|
|
$this->insertSql .= $sqlWalker->walkFromClause($fromClause); |
82
|
4 |
|
|
83
|
|
|
// 2. Create statement used in UPDATE ... WHERE ... IN (subselect) |
84
|
4 |
|
$updateSQLTemplate = sprintf( |
85
|
4 |
|
'UPDATE %%s SET %%s WHERE (%s) IN (SELECT %s FROM %s)', |
86
|
4 |
|
$idColumnNameList, |
87
|
|
|
$idColumnNameList, |
88
|
|
|
$tempTable |
89
|
4 |
|
); |
90
|
|
|
|
91
|
4 |
|
// 3. Create and store UPDATE statements |
92
|
4 |
|
$hierarchyClasses = array_merge( |
93
|
|
|
array_map( |
94
|
4 |
|
function ($className) use ($em) { return $em->getClassMetadata($className); }, |
95
|
4 |
|
array_reverse($primaryClass->getSubClasses()) |
|
|
|
|
96
|
|
|
), |
97
|
4 |
|
[$primaryClass], |
98
|
|
|
$primaryClass->getAncestorsIterator()->getArrayCopy() |
|
|
|
|
99
|
|
|
); |
100
|
4 |
|
|
101
|
|
|
$i = 0; |
102
|
|
|
|
103
|
4 |
|
foreach ($hierarchyClasses as $class) { |
104
|
4 |
|
$updateSQLParts = []; |
105
|
|
|
|
106
|
4 |
|
foreach ($updateItems as $updateItem) { |
107
|
4 |
|
$field = $updateItem->pathExpression->field; |
108
|
4 |
|
$property = $class->getProperty($field); |
109
|
4 |
|
|
110
|
|
|
if ($property && ! $class->isInheritedProperty($field)) { |
111
|
4 |
|
$updateSQLParts[] = $sqlWalker->walkUpdateItem($updateItem); |
112
|
4 |
|
$newValue = $updateItem->newValue; |
113
|
|
|
|
114
|
4 |
|
if ($newValue instanceof AST\InputParameter) { |
115
|
4 |
|
$this->sqlParameters[$i][] = $newValue->name; |
116
|
4 |
|
|
117
|
|
|
++$this->numParametersInUpdateClause; |
118
|
4 |
|
} |
119
|
4 |
|
} |
120
|
4 |
|
} |
121
|
|
|
|
122
|
1 |
|
if ($updateSQLParts) { |
|
|
|
|
123
|
|
|
$this->sqlStatements[$i] = sprintf( |
124
|
|
|
$updateSQLTemplate, |
125
|
4 |
|
$class->table->getQuotedQualifiedName($platform), |
126
|
|
|
implode(', ', $updateSQLParts) |
127
|
4 |
|
); |
128
|
3 |
|
|
129
|
|
|
$i++; |
130
|
4 |
|
} |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
// Append WHERE clause to insertSql, if there is one. |
134
|
|
|
if ($AST->whereClause) { |
135
|
4 |
|
$this->insertSql .= $sqlWalker->walkWhereClause($AST->whereClause); |
|
|
|
|
136
|
4 |
|
} |
137
|
|
|
|
138
|
|
|
// 4. Store DDL for temporary identifier table. |
139
|
|
|
$columnDefinitions = []; |
140
|
|
|
|
141
|
4 |
View Code Duplication |
foreach ($idColumns as $columnName => $column) { |
|
|
|
|
142
|
3 |
|
$columnDefinitions[$columnName] = [ |
143
|
|
|
'notnull' => true, |
144
|
|
|
'type' => $column->getType(), |
145
|
|
|
]; |
146
|
4 |
|
} |
147
|
|
|
|
148
|
4 |
|
$this->createTempTableSql = $platform->getCreateTemporaryTableSnippetSQL() . ' ' . $tempTable . ' (' |
149
|
4 |
|
. $platform->getColumnDeclarationListSQL($columnDefinitions) . ')'; |
150
|
4 |
|
|
151
|
4 |
|
$this->dropTempTableSql = $platform->getDropTemporaryTableSQL($tempTable); |
152
|
|
|
} |
153
|
|
|
|
154
|
4 |
|
/** |
155
|
4 |
|
* {@inheritDoc} |
156
|
4 |
|
*/ |
157
|
|
|
public function execute(Connection $conn, array $params, array $types) |
158
|
|
|
{ |
159
|
|
|
// Create temporary id table |
160
|
4 |
|
$conn->executeUpdate($this->createTempTableSql); |
161
|
4 |
|
|
162
|
|
|
try { |
163
|
4 |
|
// Insert identifiers. Parameters from the update clause are cut off. |
164
|
4 |
|
$numUpdated = $conn->executeUpdate( |
165
|
|
|
$this->insertSql, |
166
|
|
|
array_slice($params, $this->numParametersInUpdateClause), |
167
|
|
|
array_slice($types, $this->numParametersInUpdateClause) |
168
|
|
|
); |
169
|
5 |
|
|
170
|
|
|
// Execute UPDATE statements |
171
|
|
|
foreach ($this->sqlStatements as $key => $statement) { |
172
|
5 |
|
$paramValues = []; |
173
|
|
|
$paramTypes = []; |
174
|
|
|
|
175
|
|
|
if (isset($this->sqlParameters[$key])) { |
176
|
5 |
|
foreach ($this->sqlParameters[$key] as $parameterKey => $parameterName) { |
177
|
5 |
|
$paramValues[] = $params[$parameterKey]; |
178
|
5 |
|
$paramTypes[] = isset($types[$parameterKey]) |
179
|
5 |
|
? $types[$parameterKey] |
180
|
|
|
: ParameterTypeInferer::inferType($params[$parameterKey]) |
181
|
|
|
; |
182
|
|
|
} |
183
|
5 |
|
} |
184
|
5 |
|
|
185
|
5 |
|
$conn->executeUpdate($statement, $paramValues, $paramTypes); |
186
|
|
|
} |
187
|
5 |
|
} catch (\Exception $exception) { |
188
|
3 |
|
// FAILURE! Drop temporary table to avoid possible collisions |
189
|
3 |
|
$conn->executeUpdate($this->dropTempTableSql); |
190
|
3 |
|
|
191
|
3 |
|
// Re-throw exception |
192
|
3 |
|
throw $exception; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
// Drop temporary table |
196
|
|
|
$conn->executeUpdate($this->dropTempTableSql); |
197
|
5 |
|
|
198
|
|
|
return $numUpdated; |
|
|
|
|
199
|
|
|
} |
200
|
|
|
} |
201
|
|
|
|
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.
If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.