Add explicit `return null;` statements
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / package / PackageInstallationSQLParser.class.php
CommitLineData
08e8d4d0 1<?php
a9229942 2
567b9611 3namespace wcf\system\package;
a9229942 4
08e8d4d0
AE
5use wcf\data\package\Package;
6use wcf\system\database\util\SQLParser;
7use wcf\system\exception\SystemException;
8use wcf\system\WCF;
9
10/**
a17de04e 11 * Extends SQLParser by testing and logging functions.
a9229942
TD
12 *
13 * @author Marcel Werk
14 * @copyright 2001-2019 WoltLab GmbH
15 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
16 * @package WoltLabSuite\Core\System\Package
08e8d4d0 17 */
a9229942
TD
18class PackageInstallationSQLParser extends SQLParser
19{
20 /**
21 * package object
22 * @var Package
23 */
24 protected $package;
25
26 /**
27 * activates the testing mode
28 * @var bool
29 */
30 protected $test = false;
31
32 /**
33 * installation type
34 * @var string
35 */
36 protected $action = 'install';
37
38 /**
39 * list of existing database tables
40 * @var array
41 */
42 protected $existingTables = [];
43
44 /**
45 * list of logged tables
46 * @var array
47 */
48 protected $knownTables = [];
49
50 /**
51 * list of conflicted database tables
52 * @var array
53 */
54 protected $conflicts = [];
55
56 /**
57 * list of created/deleted tables
58 * @var array
59 */
60 protected $tableLog = [];
61
62 /**
63 * list of created/deleted columns
64 * @var array
65 */
66 protected $columnLog = [];
67
68 /**
69 * list of created/deleted indices
70 * @var array
71 */
72 protected $indexLog = [];
73
74 /**
75 * Creates a new PackageInstallationSQLParser object.
76 *
77 * @param string $queries
78 * @param Package $package
79 * @param string $action
80 */
81 public function __construct($queries, Package $package, $action = 'install')
82 {
83 $this->package = $package;
84 $this->action = $action;
85
86 parent::__construct($queries);
87 }
88
89 /**
90 * Performs a test of the given queries.
91 *
92 * @return array conflicts
93 */
94 public function test()
95 {
96 $this->conflicts = [];
97
98 // get all existing tables from database
99 $this->existingTables = WCF::getDB()->getEditor()->getTableNames();
100
101 // get logged tables
102 $this->getKnownTables();
103
104 // enable testing mode
105 $this->test = true;
106
107 // run test
108 $this->execute();
109
110 // disable testing mode
111 $this->test = false;
112
113 // return conflicts
114 return $this->conflicts;
115 }
116
117 /**
118 * Logs executed sql queries
119 */
120 public function log()
121 {
122 // tables
123 foreach ($this->tableLog as $logEntry) {
124 $sql = "DELETE FROM wcf" . WCF_N . "_package_installation_sql_log
125 WHERE sqlTable = ?";
126 $statement = WCF::getDB()->prepareStatement($sql);
127 $statement->execute([$logEntry['tableName']]);
128
129 if ($logEntry['action'] == 'insert') {
130 $sql = "INSERT INTO wcf" . WCF_N . "_package_installation_sql_log
131 (packageID, sqlTable)
132 VALUES (?, ?)";
133 $statement = WCF::getDB()->prepareStatement($sql);
134 $statement->execute([
135 $logEntry['packageID'],
136 $logEntry['tableName'],
137 ]);
138 }
139 }
140
141 // columns
142 if (!empty($this->columnLog)) {
143 $sql = "DELETE FROM wcf" . WCF_N . "_package_installation_sql_log
144 WHERE sqlTable = ?
145 AND sqlColumn = ?";
146 $deleteStatement = WCF::getDB()->prepareStatement($sql);
147
148 $sql = "INSERT INTO wcf" . WCF_N . "_package_installation_sql_log
149 (packageID, sqlTable, sqlColumn)
150 VALUES (?, ?, ?)";
151 $insertStatement = WCF::getDB()->prepareStatement($sql);
152
153 foreach ($this->columnLog as $logEntry) {
154 $deleteStatement->execute([
155 $logEntry['tableName'],
156 $logEntry['columnName'],
157 ]);
158
159 if ($logEntry['action'] == 'insert') {
160 $insertStatement->execute([
161 $logEntry['packageID'],
162 $logEntry['tableName'],
163 $logEntry['columnName'],
164 ]);
165 }
166 }
167 }
168
169 // indices
170 if (!empty($this->indexLog)) {
171 $sql = "DELETE FROM wcf" . WCF_N . "_package_installation_sql_log
172 WHERE sqlTable = ?
173 AND sqlIndex = ?";
174 $deleteStatement = WCF::getDB()->prepareStatement($sql);
175
176 $sql = "INSERT INTO wcf" . WCF_N . "_package_installation_sql_log
177 (packageID, sqlTable, sqlIndex)
178 VALUES (?, ?, ?)";
179 $insertStatement = WCF::getDB()->prepareStatement($sql);
180
181 foreach ($this->indexLog as $logEntry) {
182 $deleteStatement->execute([
183 $logEntry['tableName'],
184 $logEntry['indexName'],
185 ]);
186
187 if ($logEntry['action'] == 'insert') {
188 $insertStatement->execute([
189 $logEntry['packageID'],
190 $logEntry['tableName'],
191 $logEntry['indexName'],
192 ]);
193 }
194 }
195 }
196 }
197
198 /**
199 * Fetches known sql tables and their owners from installation log.
200 */
201 protected function getKnownTables()
202 {
203 $sql = "SELECT packageID, sqlTable
204 FROM wcf" . WCF_N . "_package_installation_sql_log
205 WHERE sqlColumn = ''
206 AND sqlIndex = ''";
207 $statement = WCF::getDB()->prepareStatement($sql);
208 $statement->execute();
209 $this->knownTables = $statement->fetchMap('sqlTable', 'packageID');
210 }
211
212 /**
213 * Returns the owner of a specific database table column.
214 *
215 * @param string $tableName
216 * @param string $columnName
c0b28aa2 217 * @return int|null package id
a9229942
TD
218 */
219 protected function getColumnOwnerID($tableName, $columnName)
220 {
221 $sql = "SELECT packageID
222 FROM wcf" . WCF_N . "_package_installation_sql_log
223 WHERE sqlTable = ?
224 AND sqlColumn = ?";
225 $statement = WCF::getDB()->prepareStatement($sql);
226 $statement->execute([
227 $tableName,
228 $columnName,
229 ]);
230 $row = $statement->fetchArray();
231 if (!empty($row['packageID'])) {
232 return $row['packageID'];
233 } elseif (isset($this->knownTables[$tableName])) {
234 return $this->knownTables[$tableName];
235 } else {
c0b28aa2 236 return null;
a9229942
TD
237 }
238 }
239
240 /**
241 * Returns the owner of a specific database index.
242 *
243 * @param string $tableName
244 * @param string $indexName
245 * @return int package id
246 */
247 protected function getIndexOwnerID($tableName, $indexName)
248 {
249 $sql = "SELECT packageID
250 FROM wcf" . WCF_N . "_package_installation_sql_log
251 WHERE sqlTable = ?
252 AND sqlIndex = ?";
253 $statement = WCF::getDB()->prepareStatement($sql);
254 $statement->execute([
255 $tableName,
256 $indexName,
257 ]);
258 $row = $statement->fetchArray();
259 if (!empty($row['packageID'])) {
260 return $row['packageID'];
261 } elseif (isset($this->knownTables[$tableName])) {
262 return $this->knownTables[$tableName];
263 } else {
264 return;
265 }
266 }
267
268 /**
269 * @inheritDoc
270 */
271 protected function executeCreateTableStatement($tableName, $columns, $indices = [])
272 {
273 if ($this->test) {
274 if (\in_array($tableName, $this->existingTables)) {
275 if (
276 isset($this->knownTables[$tableName])
277 && $this->knownTables[$tableName] != $this->package->packageID
278 ) {
279 throw new SystemException("Cannot recreate table '" . $tableName . "'. A package can only overwrite own tables.");
280 } else {
281 if (!isset($this->conflicts['CREATE TABLE'])) {
282 $this->conflicts['CREATE TABLE'] = [];
283 }
284 $this->conflicts['CREATE TABLE'][] = $tableName;
285 }
286 }
287 } else {
288 // log
289 $this->tableLog[] = [
290 'tableName' => $tableName,
291 'packageID' => $this->package->packageID,
292 'action' => 'insert',
293 ];
294
295 // execute
296 parent::executeCreateTableStatement($tableName, $columns, $indices);
297 }
298 }
299
300 /**
301 * @inheritDoc
302 */
303 protected function executeAddColumnStatement($tableName, $columnName, $columnData)
304 {
305 if ($this->test) {
306 if (!isset($this->knownTables[$tableName])) {
307 throw new SystemException("Cannot add column '" . $columnName . "' to table '" . $tableName . "'.");
308 }
309 } else {
310 // log
311 $this->columnLog[] = [
312 'tableName' => $tableName,
313 'columnName' => $columnName,
314 'packageID' => $this->package->packageID,
315 'action' => 'insert',
316 ];
317
318 // execute
319 parent::executeAddColumnStatement($tableName, $columnName, $columnData);
320 }
321 }
322
323 /**
324 * @inheritDoc
325 */
326 protected function executeAlterColumnStatement($tableName, $oldColumnName, $newColumnName, $newColumnData)
327 {
328 if ($this->test) {
329 if ($ownerPackageID = $this->getColumnOwnerID($tableName, $oldColumnName)) {
330 if ($ownerPackageID != $this->package->packageID) {
331 throw new SystemException("Cannot alter column '" . $oldColumnName . "'. A package can only change own columns.");
332 }
333 }
334 } else {
335 // log
336 if ($oldColumnName != $newColumnName) {
337 $this->columnLog[] = [
338 'tableName' => $tableName,
339 'columnName' => $oldColumnName,
340 'packageID' => $this->package->packageID,
341 'action' => 'delete',
342 ];
343 $this->columnLog[] = [
344 'tableName' => $tableName,
345 'columnName' => $newColumnName,
346 'packageID' => $this->package->packageID,
347 'action' => 'insert',
348 ];
349 }
350
351 // execute
352 parent::executeAlterColumnStatement($tableName, $oldColumnName, $newColumnName, $newColumnData);
353 }
354 }
355
356 /**
357 * @inheritDoc
358 */
359 protected function executeAddIndexStatement($tableName, $indexName, $indexData)
360 {
361 if (!$this->test) {
362 // log
363 $this->indexLog[] = [
364 'tableName' => $tableName,
365 'indexName' => $indexName,
366 'packageID' => $this->package->packageID,
367 'action' => 'insert',
368 ];
369
370 // execute
371 parent::executeAddIndexStatement($tableName, $indexName, $indexData);
372 }
373 }
374
375 /**
376 * @inheritDoc
377 */
378 protected function executeAddForeignKeyStatement($tableName, $indexName, $indexData)
379 {
380 if (!$this->test) {
381 // log
382 $this->indexLog[] = [
383 'tableName' => $tableName,
384 'indexName' => $indexName,
385 'packageID' => $this->package->packageID,
386 'action' => 'insert',
387 ];
388
389 // execute
390 parent::executeAddForeignKeyStatement($tableName, $indexName, $indexData);
391 }
392 }
393
394 /**
395 * @inheritDoc
396 */
397 protected function executeDropColumnStatement($tableName, $columnName)
398 {
399 if ($this->test) {
400 if ($ownerPackageID = $this->getColumnOwnerID($tableName, $columnName)) {
401 if ($ownerPackageID != $this->package->packageID) {
402 throw new SystemException("Cannot drop column '" . $columnName . "'. A package can only drop own columns.");
403 }
404 }
405 } else {
406 // log
407 $this->columnLog[] = [
408 'tableName' => $tableName,
409 'columnName' => $columnName,
410 'packageID' => $this->package->packageID,
411 'action' => 'delete',
412 ];
413
414 // execute
415 parent::executeDropColumnStatement($tableName, $columnName);
416 }
417 }
418
419 /**
420 * @inheritDoc
421 */
422 protected function executeDropIndexStatement($tableName, $indexName)
423 {
424 if ($this->test) {
425 if ($ownerPackageID = $this->getIndexOwnerID($tableName, $indexName)) {
426 if ($ownerPackageID != $this->package->packageID) {
427 throw new SystemException("Cannot drop index '" . $indexName . "'. A package can only drop own indices.");
428 }
429 }
430 } else {
431 // log
432 $this->indexLog[] = [
433 'tableName' => $tableName,
434 'indexName' => $indexName,
435 'packageID' => $this->package->packageID,
436 'action' => 'delete',
437 ];
438
439 // execute
440 parent::executeDropIndexStatement($tableName, $indexName);
441 }
442 }
443
444 /**
445 * @inheritDoc
446 */
447 protected function executeDropPrimaryKeyStatement($tableName)
448 {
449 if ($this->test) {
450 if ($ownerPackageID = $this->getIndexOwnerID($tableName, '')) {
451 if ($ownerPackageID != $this->package->packageID) {
452 throw new SystemException("Cannot drop primary key from '" . $tableName . "'. A package can only drop own indices.");
453 }
454 }
455 } else {
456// // log
457// $this->indexLog[] = array('tableName' => $tableName, 'indexName' => '', 'packageID' => $this->package->packageID, 'action' => 'delete');
458
459 // execute
460 parent::executeDropPrimaryKeyStatement($tableName);
461 }
462 }
463
464 /**
465 * @inheritDoc
466 */
467 protected function executeDropForeignKeyStatement($tableName, $indexName)
468 {
469 if ($this->test) {
470 if ($ownerPackageID = $this->getIndexOwnerID($tableName, $indexName)) {
471 if ($ownerPackageID != $this->package->packageID) {
472 throw new SystemException("Cannot drop index '" . $indexName . "'. A package can only drop own indices.");
473 }
474 }
475 } else {
476 // log
477 $this->indexLog[] = [
478 'tableName' => $tableName,
479 'indexName' => $indexName,
480 'packageID' => $this->package->packageID,
481 'action' => 'delete',
482 ];
483
484 // execute
485 parent::executeDropForeignKeyStatement($tableName, $indexName);
486 }
487 }
488
489 /**
490 * @inheritDoc
491 */
492 protected function executeDropTableStatement($tableName)
493 {
494 if ($this->test) {
495 if (\in_array($tableName, $this->existingTables)) {
496 if (isset($this->knownTables[$tableName]) && $this->knownTables[$tableName] != $this->package->packageID) {
497 throw new SystemException("Cannot drop table '" . $tableName . "'. A package can only drop own tables.");
498 }
499 }
500 } else {
501 // log
502 $this->tableLog[] = [
503 'tableName' => $tableName,
504 'packageID' => $this->package->packageID,
505 'action' => 'delete',
506 ];
507
508 // execute
509 parent::executeDropTableStatement($tableName);
510 }
511 }
512
513 /**
514 * @inheritDoc
515 */
516 protected function executeStandardStatement($query)
517 {
518 if (!$this->test) {
519 parent::executeStandardStatement($query);
520 }
521 }
08e8d4d0 522}