* @package WoltLabSuite\Core\System\Package */ class PackageInstallationSQLParser extends SQLParser { /** * package object * @var Package */ protected $package = null; /** * activates the testing mode * @var boolean */ protected $test = false; /** * installation type * @var string */ protected $action = 'install'; /** * list of existing database tables * @var array */ protected $existingTables = []; /** * list of logged tables * @var array */ protected $knownTables = []; /** * list of conflicted database tables * @var array */ protected $conflicts = []; /** * list of created/deleted tables * @var array */ protected $tableLog = []; /** * list of created/deleted columns * @var array */ protected $columnLog = []; /** * list of created/deleted indices * @var array */ protected $indexLog = []; /** * Creates a new PackageInstallationSQLParser object. * * @param string $queries * @param Package $package * @param string $action */ public function __construct($queries, Package $package, $action = 'install') { $this->package = $package; $this->action = $action; parent::__construct($queries); } /** * Performs a test of the given queries. * * @return array conflicts */ public function test() { $this->conflicts = []; // get all existing tables from database $this->existingTables = WCF::getDB()->getEditor()->getTableNames(); // get logged tables $this->getKnownTables(); // enable testing mode $this->test = true; // run test $this->execute(); // disable testing mode $this->test = false; // return conflicts return $this->conflicts; } /** * Logs executed sql queries */ public function log() { // tables foreach ($this->tableLog as $logEntry) { $sql = "DELETE FROM wcf".WCF_N."_package_installation_sql_log WHERE sqlTable = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([$logEntry['tableName']]); if ($logEntry['action'] == 'insert') { $sql = "INSERT INTO wcf".WCF_N."_package_installation_sql_log (packageID, sqlTable) VALUES (?, ?)"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([ $logEntry['packageID'], $logEntry['tableName'] ]); } } // columns if (!empty($this->columnLog)) { $sql = "DELETE FROM wcf".WCF_N."_package_installation_sql_log WHERE sqlTable = ? AND sqlColumn = ?"; $deleteStatement = WCF::getDB()->prepareStatement($sql); $sql = "INSERT INTO wcf".WCF_N."_package_installation_sql_log (packageID, sqlTable, sqlColumn) VALUES (?, ?, ?)"; $insertStatement = WCF::getDB()->prepareStatement($sql); foreach ($this->columnLog as $logEntry) { $deleteStatement->execute([ $logEntry['tableName'], $logEntry['columnName'] ]); if ($logEntry['action'] == 'insert') { $insertStatement->execute([ $logEntry['packageID'], $logEntry['tableName'], $logEntry['columnName'] ]); } } } // indices if (!empty($this->indexLog)) { $sql = "DELETE FROM wcf".WCF_N."_package_installation_sql_log WHERE sqlTable = ? AND sqlIndex = ?"; $deleteStatement = WCF::getDB()->prepareStatement($sql); $sql = "INSERT INTO wcf".WCF_N."_package_installation_sql_log (packageID, sqlTable, sqlIndex) VALUES (?, ?, ?)"; $insertStatement = WCF::getDB()->prepareStatement($sql); foreach ($this->indexLog as $logEntry) { $deleteStatement->execute([ $logEntry['tableName'], $logEntry['indexName'] ]); if ($logEntry['action'] == 'insert') { $insertStatement->execute([ $logEntry['packageID'], $logEntry['tableName'], $logEntry['indexName'] ]); } } } } /** * Fetches known sql tables and their owners from installation log. */ protected function getKnownTables() { $sql = "SELECT packageID, sqlTable FROM wcf".WCF_N."_package_installation_sql_log WHERE sqlColumn = '' AND sqlIndex = ''"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute(); $this->knownTables = $statement->fetchMap('sqlTable', 'packageID'); } /** * Returns the owner of a specific database table column. * * @param string $tableName * @param string $columnName * @return integer package id */ protected function getColumnOwnerID($tableName, $columnName) { $sql = "SELECT packageID FROM wcf".WCF_N."_package_installation_sql_log WHERE sqlTable = ? AND sqlColumn = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([ $tableName, $columnName ]); $row = $statement->fetchArray(); if (!empty($row['packageID'])) return $row['packageID']; else if (isset($this->knownTables[$tableName])) return $this->knownTables[$tableName]; else return null; } /** * Returns the owner of a specific database index. * * @param string $tableName * @param string $indexName * @return integer package id */ protected function getIndexOwnerID($tableName, $indexName) { $sql = "SELECT packageID FROM wcf".WCF_N."_package_installation_sql_log WHERE sqlTable = ? AND sqlIndex = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute([ $tableName, $indexName ]); $row = $statement->fetchArray(); if (!empty($row['packageID'])) return $row['packageID']; else if (isset($this->knownTables[$tableName])) return $this->knownTables[$tableName]; else return null; } /** * @inheritDoc */ protected function executeCreateTableStatement($tableName, $columns, $indices = []) { if ($this->test) { if (in_array($tableName, $this->existingTables)) { if (isset($this->knownTables[$tableName]) && $this->knownTables[$tableName] != $this->package->packageID) { throw new SystemException("Cannot recreate table '".$tableName."'. A package can only overwrite own tables."); } else { if (!isset($this->conflicts['CREATE TABLE'])) $this->conflicts['CREATE TABLE'] = []; $this->conflicts['CREATE TABLE'][] = $tableName; } } } else { // log $this->tableLog[] = ['tableName' => $tableName, 'packageID' => $this->package->packageID, 'action' => 'insert']; // execute parent::executeCreateTableStatement($tableName, $columns, $indices); } } /** * @inheritDoc */ protected function executeAddColumnStatement($tableName, $columnName, $columnData) { if ($this->test) { if (!isset($this->knownTables[$tableName])) { throw new SystemException("Cannot add column '".$columnName."' to table '".$tableName."'."); } } else { // log $this->columnLog[] = ['tableName' => $tableName, 'columnName' => $columnName, 'packageID' => $this->package->packageID, 'action' => 'insert']; // execute parent::executeAddColumnStatement($tableName, $columnName, $columnData); } } /** * @inheritDoc */ protected function executeAlterColumnStatement($tableName, $oldColumnName, $newColumnName, $newColumnData) { if ($this->test) { if ($ownerPackageID = $this->getColumnOwnerID($tableName, $oldColumnName)) { if ($ownerPackageID != $this->package->packageID) { throw new SystemException("Cannot alter column '".$oldColumnName."'. A package can only change own columns."); } } } else { // log if ($oldColumnName != $newColumnName) { $this->columnLog[] = ['tableName' => $tableName, 'columnName' => $oldColumnName, 'packageID' => $this->package->packageID, 'action' => 'delete']; $this->columnLog[] = ['tableName' => $tableName, 'columnName' => $newColumnName, 'packageID' => $this->package->packageID, 'action' => 'insert']; } // execute parent::executeAlterColumnStatement($tableName, $oldColumnName, $newColumnName, $newColumnData); } } /** * @inheritDoc */ protected function executeAddIndexStatement($tableName, $indexName, $indexData) { if (!$this->test) { // log $this->indexLog[] = ['tableName' => $tableName, 'indexName' => $indexName, 'packageID' => $this->package->packageID, 'action' => 'insert']; // execute parent::executeAddIndexStatement($tableName, $indexName, $indexData); } } /** * @inheritDoc */ protected function executeAddForeignKeyStatement($tableName, $indexName, $indexData) { if (!$this->test) { // log $this->indexLog[] = ['tableName' => $tableName, 'indexName' => $indexName, 'packageID' => $this->package->packageID, 'action' => 'insert']; // execute parent::executeAddForeignKeyStatement($tableName, $indexName, $indexData); } } /** * @inheritDoc */ protected function executeDropColumnStatement($tableName, $columnName) { if ($this->test) { if ($ownerPackageID = $this->getColumnOwnerID($tableName, $columnName)) { if ($ownerPackageID != $this->package->packageID) { throw new SystemException("Cannot drop column '".$columnName."'. A package can only drop own columns."); } } } else { // log $this->columnLog[] = ['tableName' => $tableName, 'columnName' => $columnName, 'packageID' => $this->package->packageID, 'action' => 'delete']; // execute parent::executeDropColumnStatement($tableName, $columnName); } } /** * @inheritDoc */ protected function executeDropIndexStatement($tableName, $indexName) { if ($this->test) { if ($ownerPackageID = $this->getIndexOwnerID($tableName, $indexName)) { if ($ownerPackageID != $this->package->packageID) { throw new SystemException("Cannot drop index '".$indexName."'. A package can only drop own indices."); } } } else { // log $this->indexLog[] = ['tableName' => $tableName, 'indexName' => $indexName, 'packageID' => $this->package->packageID, 'action' => 'delete']; // execute parent::executeDropIndexStatement($tableName, $indexName); } } /** * @inheritDoc */ protected function executeDropPrimaryKeyStatement($tableName) { if ($this->test) { if ($ownerPackageID = $this->getIndexOwnerID($tableName, '')) { if ($ownerPackageID != $this->package->packageID) { throw new SystemException("Cannot drop primary key from '".$tableName."'. A package can only drop own indices."); } } } else { // // log // $this->indexLog[] = array('tableName' => $tableName, 'indexName' => '', 'packageID' => $this->package->packageID, 'action' => 'delete'); // execute parent::executeDropPrimaryKeyStatement($tableName); } } /** * @inheritDoc */ protected function executeDropForeignKeyStatement($tableName, $indexName) { if ($this->test) { if ($ownerPackageID = $this->getIndexOwnerID($tableName, $indexName)) { if ($ownerPackageID != $this->package->packageID) { throw new SystemException("Cannot drop index '".$indexName."'. A package can only drop own indices."); } } } else { // log $this->indexLog[] = ['tableName' => $tableName, 'indexName' => $indexName, 'packageID' => $this->package->packageID, 'action' => 'delete']; // execute parent::executeDropForeignKeyStatement($tableName, $indexName); } } /** * @inheritDoc */ protected function executeDropTableStatement($tableName) { if ($this->test) { if (in_array($tableName, $this->existingTables)) { if (isset($this->knownTables[$tableName]) && $this->knownTables[$tableName] != $this->package->packageID) { throw new SystemException("Cannot drop table '".$tableName."'. A package can only drop own tables."); } } } else { // log $this->tableLog[] = ['tableName' => $tableName, 'packageID' => $this->package->packageID, 'action' => 'delete']; // execute parent::executeDropTableStatement($tableName); } } /** * @inheritDoc */ protected function executeStandardStatement($query) { if (!$this->test) { parent::executeStandardStatement($query); } } }