OpenXE/phpwf/plugins/class.databaseupgrade.php
2021-05-21 08:49:41 +02:00

727 lines
18 KiB
PHP

<?php
/*
**** COPYRIGHT & LICENSE NOTICE *** DO NOT REMOVE ****
*
* Xentral (c) Xentral ERP Sorftware GmbH, Fuggerstrasse 11, D-86150 Augsburg, * Germany 2019
*
* This file is licensed under the Embedded Projects General Public License *Version 3.1.
*
* You should have received a copy of this license from your vendor and/or *along with this file; If not, please visit www.wawision.de/Lizenzhinweis
* to obtain the text of the corresponding license version.
*
**** END OF COPYRIGHT & LICENSE NOTICE *** DO NOT REMOVE ****
*/
?>
<?php
final class DatabaseUpgrade
{
/** @var Application $app */
private $app;
/** @var array $CheckColumnTableCache */
private $CheckColumnTableCache;
/** @var bool $check_column_missing_run */
private $check_column_missing_run=false;
/** @var array $check_column_missing */
private $check_column_missing=array();
/** @var array $check_index_missing */
private $check_index_missing=array();
/** @var array */
private $allTables = [];
/** @var array */
private $indexe = [];
/**
* @param Application $app
*/
public function __construct($app)
{
$this->app = $app;
}
public function emptyTableCache(){
$this->CheckColumnTableCache = [];
$this->allTables = [];
$this->indexe = [];
}
/**
* @var bool $force
*
* @return array
*/
public function getAllTables($force = false)
{
if($force || empty($this->allTables)) {
$this->allTables = $this->app->DB->SelectFirstCols('SHOW TABLES');
}
return $this->allTables;
}
/**
* @param string $table
* @param string $pk
*/
public function createTable($table, $pk = 'id')
{
$sql = "CREATE TABLE `$table` (`".$pk."` INT NOT NULL AUTO_INCREMENT, PRIMARY KEY (`".$pk."`)) ENGINE = InnoDB DEFAULT CHARSET=utf8";
$this->app->DB->Query($sql);
$this->addPrimary($table, $pk);
}
/**
* @param string $table
* @param string $pk
*/
public function addPrimary($table, $pk = 'id')
{
$this->CheckAlterTable(
"ALTER TABLE `$table`
ADD PRIMARY KEY (`".$pk."`)",
true
);
$this->CheckAlterTable(
"ALTER TABLE `$table`
MODIFY `".$pk."` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=1",
true
);
}
/**
* @param string $table
* @param bool $force
*
* @return array
*/
public function getIndexeCached($table, $force = false)
{
if($force || !isset($this->indexe[$table])){
$this->indexe[$table] = $this->app->DB->SelectArr(sprintf('SHOW INDEX FROM `%s`', $table));
if($this->indexe[$table] === null) {
$this->indexe[$table] = [];
}
}
return $this->indexe[$table];
}
/**
* @param string $table
*/
public function clearIndexCached($table)
{
if(!isset($this->indexe[$table])) {
return;
}
unset($this->indexe[$table]);
}
/**
* @param string $table
* @param string $pk
*/
public function hasPrimaryKey($table, $pk = 'id')
{
$indexe = $this->getIndexeCached($table);
if(empty($indexe)) {
return false;
}
foreach($indexe as $index) {
if($index['Column_name'] === $pk
&& $index['Key_name'] === 'PRIMARY'
&& (int)$index['Non_unique'] === 0
) {
return true;
}
}
return false;
}
/**
* @param string $table
* @param string $pk
*
* @return void
*/
function CheckTable($table, $pk = 'id')
{
if($pk === 'id') {
$tables = $this->getAllTables();
if(!empty($tables)){
if(!in_array($table, $tables)){
$this->createTable($table, $pk);
return;
}
if(!$this->hasPrimaryKey($table, $pk)) {
$this->addPrimary($table, $pk);
}
return;
}
}
$found = false;
$tables = $this->getAllTables(true);
if($tables) {
$found = in_array($table, $tables);
}
else{
$check = $this->app->DB->Select("SELECT $pk FROM `$table` LIMIT 1");
if($check) {
$found = true;
}
}
if($found==false)
{
$sql = "CREATE TABLE `$table` (`".$pk."` INT NOT NULL AUTO_INCREMENT, PRIMARY KEY (`".$pk."`)) ENGINE = InnoDB DEFAULT CHARSET=utf8";
$this->app->DB->Update($sql);
$this->CheckAlterTable("ALTER TABLE `$table`
ADD PRIMARY KEY (`".$pk."`)");
$this->CheckAlterTable("ALTER TABLE `$table`
MODIFY `".$pk."` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=1");
}
if($pk !== 'id') {
$this->CheckColumn('created_at','timestamp',$table,"DEFAULT CURRENT_TIMESTAMP NOT NULL");
}
}
/**
* @param string $column
* @param string $type
* @param string $table
* @param string $default
*
* @return void
*/
function UpdateColumn($column,$type,$table,$default="NOT NULL")
{
$fields = $this->app->DB->SelectArr("show columns from `".$table."`");
if($fields)
{
foreach($fields as $val)
{
$field_array[] = $val['Field'];
}
}
if (in_array($column, $field_array))
{
$this->app->DB->Query('ALTER TABLE `'.$table.'` CHANGE `'.$column.'` `'.$column.'` '.$type.' '.$default.';');
}
}
/**
* @param string $column
* @param string $table
*
* @return void
*/
public function DeleteColumn($column,$table)
{
$this->app->DB->Query('ALTER TABLE `'.$table.'` DROP `'.$column.'`;');
}
/**
* @param string $column
* @param string $type
* @param string $table
* @param string $default
*
* @return void
*/
public function CheckColumn($column,$type,$table,$default="")
{
if($table === 'firmendaten')
{
if($this->app->DB->Select("SELECT `id` FROM `firmendaten_werte` WHERE `name` = '$column' LIMIT 1"))return;
}
if(!isset($this->CheckColumnTableCache[$table]))
{
$tmp=$this->app->DB->SelectArr("show columns from `".$table."`");
if($tmp)
{
foreach($tmp as $val)
{
$this->CheckColumnTableCache[$table][] = $val['Field'];
//$types[$val['Field']] = strtolower($val['Type']);
}
}
}
if (isset($this->CheckColumnTableCache[$table]) && !in_array($column, $this->CheckColumnTableCache[$table]))
{
if($this->check_column_missing_run)
{
//$result = mysqli_query($this->app->DB->connection,'ALTER TABLE `'.$table.'` ADD `'.$column.'` '.$type.' '.$default.';');
$this->check_column_missing[$table][]=$column;
} else {
$result = $this->app->DB->Query('ALTER TABLE `'.$table.'` ADD `'.$column.'` '.$type.' '.$default.';');
if($table === 'firmendaten' && $this->app->DB->error())
{
if((method_exists($this->app->DB, 'errno2') && $this->app->DB->errno() == '1118')
|| strpos($this->app->DB->error(),'Row size too large') !== false
)
{
$this->ChangeFirmendatenToMyIsam();
$this->app->DB->Query('ALTER TABLE `'.$table.'` ADD `'.$column.'` '.$type.' '.$default.';');
}
}
}
}
}
/**
* @param array $indexe
*
* @return array
*/
protected function getGroupedIndexe($indexe)
{
if(empty($indexe)) {
return $indexe;
}
$return = [];
foreach($indexe as $index) {
$keyName = $index['Key_name'];
$isUnique = $index['Non_unique'] == '0';
$seq = $index['Seq_in_index'];
$columnName = $index['Column_name'];
$return[$isUnique?'unique':'index'][$keyName][(int)$seq - 1] = $columnName;
}
return $return;
}
/**
* @param array $indexe
*
* @return array
*/
protected function getDoubleIndexeFromGroupedIndexe($indexe)
{
if(empty($indexe)) {
return [];
}
$ret = [];
foreach($indexe as $type => $indexArrs) {
$columnStrings = [];
foreach($indexArrs as $indexKey => $columns) {
$columnString = implode('|', $columns);
if(in_array($columnString, $columnStrings)) {
$ret[$type][] = $indexKey;
continue;
}
$columnStrings[] = $columnString;
}
}
return $ret;
}
/**
* @param string $table
* @param array $indexe
* @param bool $noCache
*
* @return array|null
*/
public function CheckDoubleIndex($table, $indexe, $noCache = false)
{
$query = $noCache?null:$this->CheckAlterTable("SHOW INDEX FROM `$table`");
if(!$query) {
$indexeGrouped = $this->getGroupedIndexe($indexe);
$doubleIndexe = $this->getDoubleIndexeFromGroupedIndexe($indexeGrouped);
if(!empty($doubleIndexe)) {
$indexe = $this->getIndexeCached($table, true);
$indexeGrouped = $this->getGroupedIndexe($indexe);
$doubleIndexe = $this->getDoubleIndexeFromGroupedIndexe($indexeGrouped);
if(empty($doubleIndexe)) {
return $indexe;
}
foreach($doubleIndexe as $type => $doubleIndex) {
foreach($doubleIndex as $indexName) {
$this->app->DB->Query("ALTER TABLE `".$table."` DROP INDEX `".$indexName."`");
}
}
}
elseif($noCache) {
return $indexe;
}
$this->CheckAlterTable("SHOW INDEX FROM `$table`", true);
return $this->getIndexeCached($table, true);
}
if(empty($indexe) || count($indexe) == 1){
return $indexe;
}
$uniquearr = array();
$indexarr = array();
foreach($indexe as $index)
{
if($index['Key_name'] !== 'PRIMARY' && !empty($index['Column_name']))
{
if($index['Non_unique'])
{
$indexarr[$index['Key_name']][] = $index['Column_name'];
}else{
$uniquearr[$index['Key_name']][] = $index['Column_name'];
}
}
}
$cindex = count($indexarr);
$cuniqe = count($uniquearr);
$changed = false;
if($cindex > 1)
{
$check = array();
foreach($indexarr as $key => $value)
{
if(empty($value))
{
continue;
}
if(count($value) > 1){
sort($value);
}
$vstr = implode(',', $value);
if(in_array($vstr, $check))
{
$this->app->DB->Query("DROP INDEX `".$key."` ON `".$table."`");
$changed = true;
}else{
$check[] = $vstr;
}
}
}
if($cuniqe > 1)
{
$check = array();
foreach($uniquearr as $key => $value)
{
if(empty($value))
{
continue;
}
if(count($value) > 1){
sort($value);
}
$vstr = implode(',', $value);
if(in_array($vstr, $check))
{
$this->app->DB->Query("DROP UNIQUE `".$key."` ON `".$table."`");
$changed = true;
}else{
$check[] = $vstr;
}
}
}
if($changed) {
return $this->getIndexeCached($table, true);
}
return $indexe;
}
/**
* @param string $table
* @param string|array $column
*
* @return bool
*/
public function CheckFulltextIndex($table,$column)
{
if(empty($table) || empty($column))
{
return false;
}
if(!is_array($column))
{
$column = [$column];
}
$columnmasked = [];
foreach($column as $keyColumn => $valueColumn)
{
if(!empty($valueColumn))
{
$columnmasked[] = "`$valueColumn`";
}else{
unset($column[$keyColumn]);
}
}
if(empty($column))
{
return false;
}
$columnsFound = [];
$indexe = $this->getIndexeCached($table, true);
$indexeFound = [];
if(!empty($indexe))
{
foreach($indexe as $index)
{
if($index['Index_type'] === 'FULLTEXT')
{
$indexeFound[] = $index['Column_name'];
if(!in_array($index['Column_name'], $columnsFound))
{
$columnsFound[] = $index['Column_name'];
}
}
}
$cindexeFound = count($indexeFound);
$column = count($column);
if(($column === $cindexeFound) && (count($columnsFound) === $column))
{
return true;
}
if($cindexeFound > 0)
{
return false;
}
}
$this->app->DB->Query(
"ALTER TABLE `$table`
ADD FULLTEXT INDEX `FullText`
(".implode(',',$columnmasked).");"
);
$error = $this->app->DB->error();
return empty($error);
}
/**
* @param string $table
* @param string $column
* @param bool $unique
*
* @return void
*/
function CheckIndex($table, $column, $unique = false)
{
$indexex = null;
$indexexother = null;
$indexe = $this->getIndexeCached($table);
if($indexe)
{
$indexe = $this->CheckDoubleIndex($table, $indexe, true);
foreach($indexe as $index)
{
if(is_array($column) && $index['Key_name'] !== 'PRIMARY')
{
if($unique && !$index['Non_unique'])
{
if(in_array($index['Column_name'], $column))
{
$indexex[$index['Key_name']][$index['Column_name']] = true;
}else{
$indexexother[$index['Key_name']][$index['Column_name']] = true;
}
}
elseif(!$unique){
if(in_array($index['Column_name'], $column)) {
$indexex[$index['Key_name']][$index['Column_name']] = true;
}
}
}
elseif(!is_array($column)){
if($index['Column_name'] == $column)
{
return;
}
}
}
if($this->check_column_missing_run)
{
$this->check_index_missing[$table][] = $column;
}
if(!$unique)
{
if(is_array($column))
{
if($indexex)
{
foreach($indexex as $k => $v) {
if(count($v) === 1 && count($column) > 1) {
$this->app->DB->Query("DROP INDEX `".$k."` ON `".$table."`");
$this->clearIndexCached($table);
unset($indexex[$k]);
}
}
foreach($indexex as $k => $v)
{
if(count($v) == count($column)){
return;
}
}
foreach($indexex as $k => $v)
{
if(!isset($indexexother[$k]))
{
$this->app->DB->Query("DROP INDEX `".$k."` ON `".$table."`");
$cols = null;
foreach($column as $c) {
$cols[] = "`$c`";
}
$this->CheckAlterTable("ALTER TABLE `$table` ADD INDEX(".implode(', ',$cols)."); ",true);
$this->clearIndexCached($table);
return;
}
}
}
$cols = null;
foreach($column as $c) {
$cols[] = "`$c`";
}
$this->CheckAlterTable("ALTER TABLE `$table` ADD INDEX(".implode(', ',$cols)."); ", true);
$this->clearIndexCached($table);
}
else{
$this->CheckAlterTable("ALTER TABLE `$table` ADD INDEX(`$column`); ", true);
$this->clearIndexCached($table);
}
}
else{
if(is_array($column))
{
if($indexex)
{
foreach($indexex as $k => $v)
{
if(count($v) == count($column))
{
return;
}
}
foreach($indexex as $k => $v)
{
if(!isset($indexexother[$k]))
{
$this->app->DB->Query("DROP INDEX `".$k."` ON `".$table."`");
$cols = null;
foreach($column as $c) {
$cols[] = "`$c`";
}
$this->CheckAlterTable("ALTER TABLE `$table` ADD UNIQUE(".implode(', ',$cols)."); ", true);
$this->clearIndexCached($table);
return;
}
}
}
$cols = null;
foreach($column as $c) {
$cols[] = "`$c`";
}
$this->CheckAlterTable("ALTER TABLE `$table` ADD UNIQUE(".implode(', ',$cols)."); ", true);
$this->clearIndexCached($table);
}else{
$this->CheckAlterTable("ALTER TABLE `$table` ADD UNIQUE(`$column`); ", true);
$this->clearIndexCached($table);
}
}
}
elseif(!is_array($column))
{
if(!$unique)
{
$this->CheckAlterTable("ALTER TABLE `$table` ADD INDEX(`$column`); ");
}else{
$this->CheckAlterTable("ALTER TABLE `$table` ADD UNIQUE(`$column`); ");
}
$this->clearIndexCached($table);
}
elseif(is_array($column))
{
$cols = null;
foreach($column as $c) {
$cols[] = "`$c`";
}
$this->CheckAlterTable("ALTER TABLE `$table` ADD UNIQUE(".implode(', ',$cols)."); ");
$this->clearIndexCached($table);
}
}
/**
* @param string $sql
* @param bool $force
*
* @return mysqli_result|bool
*/
function CheckAlterTable($sql, $force = false)
{
$sqlmd5 = md5($sql);
$check = $this->app->DB->Select("SELECT id FROM checkaltertable WHERE checksum='$sqlmd5' LIMIT 1");
if($check > 0 && !$force) return;
$query = $this->app->DB->Query($sql);
if($query && empty($check) && !$this->app->DB->error()){
$this->app->DB->Insert("INSERT INTO checkaltertable (id,checksum) VALUES ('','$sqlmd5')");
}
return $query;
}
/**
* @return void
*/
public function ChangeFirmendatenToMyIsam()
{
$this->app->DB->Query("ALTER TABLE firmendaten ENGINE = MyISAM;");
}
/**
* @param string $table
*
* @return array
*/
public function getSortedIndexColumnsByIndexName($table): array
{
$indexesByName = [];
$indexes = $this->app->DB->SelectArr(sprintf('SHOW INDEX FROM `%s`', $table));
if(empty($indexes)) {
return $indexesByName;
}
foreach($indexes as $index) {
$indexesByName[$index['Key_name']][] = $index['Column_name'];
}
foreach($indexesByName as $indexName => $columns) {
$columns = array_unique($columns);
sort($columns);
$indexesByName[$indexName] = $columns;
}
return $indexesByName;
}
/**
* @deprecated will be removed in 21.4
*
* @param string $table
* @param array $columns
*/
public function dropIndex($table, $columns): void
{
if(empty($table) || empty($columns)) {
return;
}
$columns = array_unique($columns);
sort($columns);
$countColumns = count($columns);
$indexes = $this->getSortedIndexColumnsByIndexName($table);
if(empty($indexes)) {
return;
}
foreach($indexes as $indexName => $indexColumns) {
if(count($indexColumns) !== $countColumns) {
continue;
}
if(count(array_intersect($indexColumns, $columns)) === $countColumns) {
$this->app->DB->Query(sprintf('ALTER TABLE `%s` DROP INDEX `%s`', $table, $indexName));
}
}
}
}