2 declare(strict_types
=1);
3 namespace wcf\system\package
;
4 use wcf\data\package\Package
;
5 use wcf\system\database\util\SQLParser
;
6 use wcf\system\exception\SystemException
;
10 * Extends SQLParser by testing and logging functions.
13 * @copyright 2001-2018 WoltLab GmbH
14 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
15 * @package WoltLabSuite\Core\System\Package
17 class PackageInstallationSQLParser
extends SQLParser
{
22 protected $package = null;
25 * activates the testing mode
28 protected $test = false;
34 protected $action = 'install';
37 * list of existing database tables
40 protected $existingTables = [];
43 * list of logged tables
46 protected $knownTables = [];
49 * list of conflicted database tables
52 protected $conflicts = [];
55 * list of created/deleted tables
58 protected $tableLog = [];
61 * list of created/deleted columns
64 protected $columnLog = [];
67 * list of created/deleted indices
70 protected $indexLog = [];
73 * Creates a new PackageInstallationSQLParser object.
75 * @param string $queries
76 * @param Package $package
77 * @param string $action
79 public function __construct($queries, Package
$package, $action = 'install') {
80 $this->package
= $package;
81 $this->action
= $action;
83 parent
::__construct($queries);
87 * Performs a test of the given queries.
89 * @return array conflicts
91 public function test() {
92 $this->conflicts
= [];
94 // get all existing tables from database
95 $this->existingTables
= WCF
::getDB()->getEditor()->getTableNames();
98 $this->getKnownTables();
100 // enable testing mode
106 // disable testing mode
110 return $this->conflicts
;
114 * Logs executed sql queries
116 public function log() {
118 foreach ($this->tableLog
as $logEntry) {
119 $sql = "DELETE FROM wcf".WCF_N
."_package_installation_sql_log
121 $statement = WCF
::getDB()->prepareStatement($sql);
122 $statement->execute([$logEntry['tableName']]);
124 if ($logEntry['action'] == 'insert') {
125 $sql = "INSERT INTO wcf".WCF_N
."_package_installation_sql_log
126 (packageID, sqlTable)
128 $statement = WCF
::getDB()->prepareStatement($sql);
129 $statement->execute([
130 $logEntry['packageID'],
131 $logEntry['tableName']
137 if (!empty($this->columnLog
)) {
138 $sql = "DELETE FROM wcf".WCF_N
."_package_installation_sql_log
141 $deleteStatement = WCF
::getDB()->prepareStatement($sql);
143 $sql = "INSERT INTO wcf".WCF_N
."_package_installation_sql_log
144 (packageID, sqlTable, sqlColumn)
146 $insertStatement = WCF
::getDB()->prepareStatement($sql);
148 foreach ($this->columnLog
as $logEntry) {
149 $deleteStatement->execute([
150 $logEntry['tableName'],
151 $logEntry['columnName']
154 if ($logEntry['action'] == 'insert') {
155 $insertStatement->execute([
156 $logEntry['packageID'],
157 $logEntry['tableName'],
158 $logEntry['columnName']
165 if (!empty($this->indexLog
)) {
166 $sql = "DELETE FROM wcf".WCF_N
."_package_installation_sql_log
169 $deleteStatement = WCF
::getDB()->prepareStatement($sql);
171 $sql = "INSERT INTO wcf".WCF_N
."_package_installation_sql_log
172 (packageID, sqlTable, sqlIndex)
174 $insertStatement = WCF
::getDB()->prepareStatement($sql);
176 foreach ($this->indexLog
as $logEntry) {
177 $deleteStatement->execute([
178 $logEntry['tableName'],
179 $logEntry['indexName']
182 if ($logEntry['action'] == 'insert') {
183 $insertStatement->execute([
184 $logEntry['packageID'],
185 $logEntry['tableName'],
186 $logEntry['indexName']
194 * Fetches known sql tables and their owners from installation log.
196 protected function getKnownTables() {
197 $sql = "SELECT packageID, sqlTable
198 FROM wcf".WCF_N
."_package_installation_sql_log
201 $statement = WCF
::getDB()->prepareStatement($sql);
202 $statement->execute();
203 $this->knownTables
= $statement->fetchMap('sqlTable', 'packageID');
207 * Returns the owner of a specific database table column.
209 * @param string $tableName
210 * @param string $columnName
211 * @return integer package id
213 protected function getColumnOwnerID($tableName, $columnName) {
214 $sql = "SELECT packageID
215 FROM wcf".WCF_N
."_package_installation_sql_log
218 $statement = WCF
::getDB()->prepareStatement($sql);
219 $statement->execute([
223 $row = $statement->fetchArray();
224 if (!empty($row['packageID'])) return $row['packageID'];
225 else if (isset($this->knownTables
[$tableName])) return $this->knownTables
[$tableName];
230 * Returns the owner of a specific database index.
232 * @param string $tableName
233 * @param string $indexName
234 * @return integer package id
236 protected function getIndexOwnerID($tableName, $indexName) {
237 $sql = "SELECT packageID
238 FROM wcf".WCF_N
."_package_installation_sql_log
241 $statement = WCF
::getDB()->prepareStatement($sql);
242 $statement->execute([
246 $row = $statement->fetchArray();
247 if (!empty($row['packageID'])) return $row['packageID'];
248 else if (isset($this->knownTables
[$tableName])) return $this->knownTables
[$tableName];
255 protected function executeCreateTableStatement($tableName, $columns, $indices = []) {
257 if (in_array($tableName, $this->existingTables
)) {
258 if (isset($this->knownTables
[$tableName]) && $this->knownTables
[$tableName] != $this->package
->packageID
) {
259 throw new SystemException("Cannot recreate table '".$tableName."'. A package can only overwrite own tables.");
262 if (!isset($this->conflicts
['CREATE TABLE'])) $this->conflicts
['CREATE TABLE'] = [];
263 $this->conflicts
['CREATE TABLE'][] = $tableName;
269 $this->tableLog
[] = ['tableName' => $tableName, 'packageID' => $this->package
->packageID
, 'action' => 'insert'];
272 parent
::executeCreateTableStatement($tableName, $columns, $indices);
279 protected function executeAddColumnStatement($tableName, $columnName, $columnData) {
281 if (!isset($this->knownTables
[$tableName])) {
282 throw new SystemException("Cannot add column '".$columnName."' to table '".$tableName."'.");
287 $this->columnLog
[] = ['tableName' => $tableName, 'columnName' => $columnName, 'packageID' => $this->package
->packageID
, 'action' => 'insert'];
290 parent
::executeAddColumnStatement($tableName, $columnName, $columnData);
297 protected function executeAlterColumnStatement($tableName, $oldColumnName, $newColumnName, $newColumnData) {
299 if ($ownerPackageID = $this->getColumnOwnerID($tableName, $oldColumnName)) {
300 if ($ownerPackageID != $this->package
->packageID
) {
301 throw new SystemException("Cannot alter column '".$oldColumnName."'. A package can only change own columns.");
307 if ($oldColumnName != $newColumnName) {
308 $this->columnLog
[] = ['tableName' => $tableName, 'columnName' => $oldColumnName, 'packageID' => $this->package
->packageID
, 'action' => 'delete'];
309 $this->columnLog
[] = ['tableName' => $tableName, 'columnName' => $newColumnName, 'packageID' => $this->package
->packageID
, 'action' => 'insert'];
313 parent
::executeAlterColumnStatement($tableName, $oldColumnName, $newColumnName, $newColumnData);
320 protected function executeAddIndexStatement($tableName, $indexName, $indexData) {
323 $this->indexLog
[] = ['tableName' => $tableName, 'indexName' => $indexName, 'packageID' => $this->package
->packageID
, 'action' => 'insert'];
326 parent
::executeAddIndexStatement($tableName, $indexName, $indexData);
333 protected function executeAddForeignKeyStatement($tableName, $indexName, $indexData) {
336 $this->indexLog
[] = ['tableName' => $tableName, 'indexName' => $indexName, 'packageID' => $this->package
->packageID
, 'action' => 'insert'];
339 parent
::executeAddForeignKeyStatement($tableName, $indexName, $indexData);
346 protected function executeDropColumnStatement($tableName, $columnName) {
348 if ($ownerPackageID = $this->getColumnOwnerID($tableName, $columnName)) {
349 if ($ownerPackageID != $this->package
->packageID
) {
350 throw new SystemException("Cannot drop column '".$columnName."'. A package can only drop own columns.");
356 $this->columnLog
[] = ['tableName' => $tableName, 'columnName' => $columnName, 'packageID' => $this->package
->packageID
, 'action' => 'delete'];
359 parent
::executeDropColumnStatement($tableName, $columnName);
366 protected function executeDropIndexStatement($tableName, $indexName) {
368 if ($ownerPackageID = $this->getIndexOwnerID($tableName, $indexName)) {
369 if ($ownerPackageID != $this->package
->packageID
) {
370 throw new SystemException("Cannot drop index '".$indexName."'. A package can only drop own indices.");
376 $this->indexLog
[] = ['tableName' => $tableName, 'indexName' => $indexName, 'packageID' => $this->package
->packageID
, 'action' => 'delete'];
379 parent
::executeDropIndexStatement($tableName, $indexName);
386 protected function executeDropPrimaryKeyStatement($tableName) {
388 if ($ownerPackageID = $this->getIndexOwnerID($tableName, '')) {
389 if ($ownerPackageID != $this->package
->packageID
) {
390 throw new SystemException("Cannot drop primary key from '".$tableName."'. A package can only drop own indices.");
396 // $this->indexLog[] = array('tableName' => $tableName, 'indexName' => '', 'packageID' => $this->package->packageID, 'action' => 'delete');
399 parent
::executeDropPrimaryKeyStatement($tableName);
406 protected function executeDropForeignKeyStatement($tableName, $indexName) {
408 if ($ownerPackageID = $this->getIndexOwnerID($tableName, $indexName)) {
409 if ($ownerPackageID != $this->package
->packageID
) {
410 throw new SystemException("Cannot drop index '".$indexName."'. A package can only drop own indices.");
416 $this->indexLog
[] = ['tableName' => $tableName, 'indexName' => $indexName, 'packageID' => $this->package
->packageID
, 'action' => 'delete'];
419 parent
::executeDropForeignKeyStatement($tableName, $indexName);
426 protected function executeDropTableStatement($tableName) {
428 if (in_array($tableName, $this->existingTables
)) {
429 if (isset($this->knownTables
[$tableName]) && $this->knownTables
[$tableName] != $this->package
->packageID
) {
430 throw new SystemException("Cannot drop table '".$tableName."'. A package can only drop own tables.");
436 $this->tableLog
[] = ['tableName' => $tableName, 'packageID' => $this->package
->packageID
, 'action' => 'delete'];
439 parent
::executeDropTableStatement($tableName);
446 protected function executeStandardStatement($query) {
448 parent
::executeStandardStatement($query);