3 namespace wcf\system\package
;
5 use wcf\data\package\Package
;
6 use wcf\system\database\util\SQLParser
;
7 use wcf\system\exception\SystemException
;
11 * Extends SQLParser by testing and logging functions.
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
18 class PackageInstallationSQLParser
extends SQLParser
27 * activates the testing mode
30 protected $test = false;
36 protected $action = 'install';
39 * list of existing database tables
42 protected $existingTables = [];
45 * list of logged tables
48 protected $knownTables = [];
51 * list of conflicted database tables
54 protected $conflicts = [];
57 * list of created/deleted tables
60 protected $tableLog = [];
63 * list of created/deleted columns
66 protected $columnLog = [];
69 * list of created/deleted indices
72 protected $indexLog = [];
75 * Creates a new PackageInstallationSQLParser object.
77 * @param string $queries
78 * @param Package $package
79 * @param string $action
81 public function __construct($queries, Package
$package, $action = 'install')
83 $this->package
= $package;
84 $this->action
= $action;
86 parent
::__construct($queries);
90 * Performs a test of the given queries.
92 * @return array conflicts
94 public function test()
96 $this->conflicts
= [];
98 // get all existing tables from database
99 $this->existingTables
= WCF
::getDB()->getEditor()->getTableNames();
102 $this->getKnownTables();
104 // enable testing mode
110 // disable testing mode
114 return $this->conflicts
;
118 * Logs executed sql queries
120 public function log()
123 foreach ($this->tableLog
as $logEntry) {
124 $sql = "DELETE FROM wcf" . WCF_N
. "_package_installation_sql_log
126 $statement = WCF
::getDB()->prepareStatement($sql);
127 $statement->execute([$logEntry['tableName']]);
129 if ($logEntry['action'] == 'insert') {
130 $sql = "INSERT INTO wcf" . WCF_N
. "_package_installation_sql_log
131 (packageID, sqlTable)
133 $statement = WCF
::getDB()->prepareStatement($sql);
134 $statement->execute([
135 $logEntry['packageID'],
136 $logEntry['tableName'],
142 if (!empty($this->columnLog
)) {
143 $sql = "DELETE FROM wcf" . WCF_N
. "_package_installation_sql_log
146 $deleteStatement = WCF
::getDB()->prepareStatement($sql);
148 $sql = "INSERT INTO wcf" . WCF_N
. "_package_installation_sql_log
149 (packageID, sqlTable, sqlColumn)
151 $insertStatement = WCF
::getDB()->prepareStatement($sql);
153 foreach ($this->columnLog
as $logEntry) {
154 $deleteStatement->execute([
155 $logEntry['tableName'],
156 $logEntry['columnName'],
159 if ($logEntry['action'] == 'insert') {
160 $insertStatement->execute([
161 $logEntry['packageID'],
162 $logEntry['tableName'],
163 $logEntry['columnName'],
170 if (!empty($this->indexLog
)) {
171 $sql = "DELETE FROM wcf" . WCF_N
. "_package_installation_sql_log
174 $deleteStatement = WCF
::getDB()->prepareStatement($sql);
176 $sql = "INSERT INTO wcf" . WCF_N
. "_package_installation_sql_log
177 (packageID, sqlTable, sqlIndex)
179 $insertStatement = WCF
::getDB()->prepareStatement($sql);
181 foreach ($this->indexLog
as $logEntry) {
182 $deleteStatement->execute([
183 $logEntry['tableName'],
184 $logEntry['indexName'],
187 if ($logEntry['action'] == 'insert') {
188 $insertStatement->execute([
189 $logEntry['packageID'],
190 $logEntry['tableName'],
191 $logEntry['indexName'],
199 * Fetches known sql tables and their owners from installation log.
201 protected function getKnownTables()
203 $sql = "SELECT packageID, sqlTable
204 FROM wcf" . WCF_N
. "_package_installation_sql_log
207 $statement = WCF
::getDB()->prepareStatement($sql);
208 $statement->execute();
209 $this->knownTables
= $statement->fetchMap('sqlTable', 'packageID');
213 * Returns the owner of a specific database table column.
215 * @param string $tableName
216 * @param string $columnName
217 * @return int package id
219 protected function getColumnOwnerID($tableName, $columnName)
221 $sql = "SELECT packageID
222 FROM wcf" . WCF_N
. "_package_installation_sql_log
225 $statement = WCF
::getDB()->prepareStatement($sql);
226 $statement->execute([
230 $row = $statement->fetchArray();
231 if (!empty($row['packageID'])) {
232 return $row['packageID'];
233 } elseif (isset($this->knownTables
[$tableName])) {
234 return $this->knownTables
[$tableName];
241 * Returns the owner of a specific database index.
243 * @param string $tableName
244 * @param string $indexName
245 * @return int package id
247 protected function getIndexOwnerID($tableName, $indexName)
249 $sql = "SELECT packageID
250 FROM wcf" . WCF_N
. "_package_installation_sql_log
253 $statement = WCF
::getDB()->prepareStatement($sql);
254 $statement->execute([
258 $row = $statement->fetchArray();
259 if (!empty($row['packageID'])) {
260 return $row['packageID'];
261 } elseif (isset($this->knownTables
[$tableName])) {
262 return $this->knownTables
[$tableName];
271 protected function executeCreateTableStatement($tableName, $columns, $indices = [])
274 if (\
in_array($tableName, $this->existingTables
)) {
276 isset($this->knownTables
[$tableName])
277 && $this->knownTables
[$tableName] != $this->package
->packageID
279 throw new SystemException("Cannot recreate table '" . $tableName . "'. A package can only overwrite own tables.");
281 if (!isset($this->conflicts
['CREATE TABLE'])) {
282 $this->conflicts
['CREATE TABLE'] = [];
284 $this->conflicts
['CREATE TABLE'][] = $tableName;
289 $this->tableLog
[] = [
290 'tableName' => $tableName,
291 'packageID' => $this->package
->packageID
,
292 'action' => 'insert',
296 parent
::executeCreateTableStatement($tableName, $columns, $indices);
303 protected function executeAddColumnStatement($tableName, $columnName, $columnData)
306 if (!isset($this->knownTables
[$tableName])) {
307 throw new SystemException("Cannot add column '" . $columnName . "' to table '" . $tableName . "'.");
311 $this->columnLog
[] = [
312 'tableName' => $tableName,
313 'columnName' => $columnName,
314 'packageID' => $this->package
->packageID
,
315 'action' => 'insert',
319 parent
::executeAddColumnStatement($tableName, $columnName, $columnData);
326 protected function executeAlterColumnStatement($tableName, $oldColumnName, $newColumnName, $newColumnData)
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.");
336 if ($oldColumnName != $newColumnName) {
337 $this->columnLog
[] = [
338 'tableName' => $tableName,
339 'columnName' => $oldColumnName,
340 'packageID' => $this->package
->packageID
,
341 'action' => 'delete',
343 $this->columnLog
[] = [
344 'tableName' => $tableName,
345 'columnName' => $newColumnName,
346 'packageID' => $this->package
->packageID
,
347 'action' => 'insert',
352 parent
::executeAlterColumnStatement($tableName, $oldColumnName, $newColumnName, $newColumnData);
359 protected function executeAddIndexStatement($tableName, $indexName, $indexData)
363 $this->indexLog
[] = [
364 'tableName' => $tableName,
365 'indexName' => $indexName,
366 'packageID' => $this->package
->packageID
,
367 'action' => 'insert',
371 parent
::executeAddIndexStatement($tableName, $indexName, $indexData);
378 protected function executeAddForeignKeyStatement($tableName, $indexName, $indexData)
382 $this->indexLog
[] = [
383 'tableName' => $tableName,
384 'indexName' => $indexName,
385 'packageID' => $this->package
->packageID
,
386 'action' => 'insert',
390 parent
::executeAddForeignKeyStatement($tableName, $indexName, $indexData);
397 protected function executeDropColumnStatement($tableName, $columnName)
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.");
407 $this->columnLog
[] = [
408 'tableName' => $tableName,
409 'columnName' => $columnName,
410 'packageID' => $this->package
->packageID
,
411 'action' => 'delete',
415 parent
::executeDropColumnStatement($tableName, $columnName);
422 protected function executeDropIndexStatement($tableName, $indexName)
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.");
432 $this->indexLog
[] = [
433 'tableName' => $tableName,
434 'indexName' => $indexName,
435 'packageID' => $this->package
->packageID
,
436 'action' => 'delete',
440 parent
::executeDropIndexStatement($tableName, $indexName);
447 protected function executeDropPrimaryKeyStatement($tableName)
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.");
457 // $this->indexLog[] = array('tableName' => $tableName, 'indexName' => '', 'packageID' => $this->package->packageID, 'action' => 'delete');
460 parent
::executeDropPrimaryKeyStatement($tableName);
467 protected function executeDropForeignKeyStatement($tableName, $indexName)
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.");
477 $this->indexLog
[] = [
478 'tableName' => $tableName,
479 'indexName' => $indexName,
480 'packageID' => $this->package
->packageID
,
481 'action' => 'delete',
485 parent
::executeDropForeignKeyStatement($tableName, $indexName);
492 protected function executeDropTableStatement($tableName)
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.");
502 $this->tableLog
[] = [
503 'tableName' => $tableName,
504 'packageID' => $this->package
->packageID
,
505 'action' => 'delete',
509 parent
::executeDropTableStatement($tableName);
516 protected function executeStandardStatement($query)
519 parent
::executeStandardStatement($query);