diff --git a/application/Espo/Core/Utils/Database/DBAL/Driver/Mysqli/Driver.php b/application/Espo/Core/Utils/Database/DBAL/Driver/Mysqli/Driver.php new file mode 100644 index 0000000000..cf4a00abc3 --- /dev/null +++ b/application/Espo/Core/Utils/Database/DBAL/Driver/Mysqli/Driver.php @@ -0,0 +1,13 @@ +newName !== false) { + $queryParts[] = 'RENAME TO ' . $diff->newName; + } + + foreach ($diff->addedColumns as $column) { + if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) { + continue; + } + + $columnArray = $column->toArray(); + $columnArray['comment'] = $this->getColumnComment($column); + $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); + } + + foreach ($diff->removedColumns as $column) { + if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) { + continue; + } + + //$queryParts[] = 'DROP ' . $column->getQuotedName($this); //espo: no needs to remove columns + } + + foreach ($diff->changedColumns as $columnDiff) { + if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) { + continue; + } + + /* @var $columnDiff \Doctrine\DBAL\Schema\ColumnDiff */ + $column = $columnDiff->column; + $columnArray = $column->toArray(); + $columnArray['comment'] = $this->getColumnComment($column); + + $queryParts[] = 'CHANGE ' . ($columnDiff->oldColumnName) . ' ' + . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); + } + + //espo: It works not correctly. It can rename some existing fields + foreach ($diff->renamedColumns as $oldColumnName => $column) { + if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) { + continue; + } + + $columnArray = $column->toArray(); + $columnArray['comment'] = $this->getColumnComment($column); + /*$queryParts[] = 'CHANGE ' . $oldColumnName . ' ' + . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); */ + $queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); //espo: fixed the problem + } //espo: END + + + $sql = array(); + $tableSql = array(); + + if ( ! $this->onSchemaAlterTable($diff, $tableSql)) { + if (count($queryParts) > 0) { + $sql[] = 'ALTER TABLE ' . $this->espoQuote($diff->name) . ' ' . implode(", ", $queryParts); + } + $sql = array_merge( + $this->getPreAlterTableIndexForeignKeySQL($diff), + $sql, + $this->getPostAlterTableIndexForeignKeySQL($diff) + ); + } + + + return array_merge($sql, $tableSql, $columnSql); + } + + + protected function getPreAlterTableIndexForeignKeySQL(TableDiff $diff) + { + $sql = array(); + $table = $diff->name; + + foreach ($diff->removedIndexes as $remKey => $remIndex) { + + foreach ($diff->addedIndexes as $addKey => $addIndex) { + if ($remIndex->getColumns() == $addIndex->getColumns()) { + + $type = ''; + if ($addIndex->isUnique()) { + $type = 'UNIQUE '; + } + + $query = 'ALTER TABLE ' . $this->espoQuote($table) . ' DROP INDEX ' . $remIndex->getName() . ', '; + $query .= 'ADD ' . $type . 'INDEX ' . $addIndex->getName(); + $query .= ' (' . $this->getIndexFieldDeclarationListSQL($addIndex->getQuotedColumns($this)) . ')'; + + $sql[] = $query; + + unset($diff->removedIndexes[$remKey]); + unset($diff->addedIndexes[$addKey]); + + break; + } + } + } + + $sql = array_merge($sql, parent::getPreAlterTableIndexForeignKeySQL($diff)); + + return $sql; + } + + + public function getDropIndexSQL($index, $table=null) + { + if ($index instanceof Index) { + $indexName = $index->getQuotedName($this); + } else if(is_string($index)) { + $indexName = $index; + } else { + throw new \InvalidArgumentException('MysqlPlatform::getDropIndexSQL() expects $index parameter to be string or \Doctrine\DBAL\Schema\Index.'); + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } else if(!is_string($table)) { + throw new \InvalidArgumentException('MysqlPlatform::getDropIndexSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); + } + + if ($index instanceof Index && $index->isPrimary()) { + // mysql primary keys are always named "PRIMARY", + // so we cannot use them in statements because of them being keyword. + return $this->getDropPrimaryKeySQL($table); + } + + return 'DROP INDEX ' . $indexName . ' ON ' . $this->espoQuote($table); + } + + protected function getDropPrimaryKeySQL($table) + { + return 'ALTER TABLE ' . $this->espoQuote($table) . ' DROP PRIMARY KEY'; + } + + public function getDropTemporaryTableSQL($table) + { + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } else if(!is_string($table)) { + throw new \InvalidArgumentException('getDropTableSQL() expects $table parameter to be string or \Doctrine\DBAL\Schema\Table.'); + } + + return 'DROP TEMPORARY TABLE ' . $this->espoQuote($table); + } + + //ESPO: fix problem with quoting table name + public function espoQuote($name) + { + if ($name instanceof Table) { + $name = $name->getQuotedName($this); + } + + if (isset($name[0]) && $name[0] != '`') { + $name = $this->quoteIdentifier($name); + } + return $name; + } + + public function getCreateForeignKeySQL(\Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey, $table) + { + $query = 'ALTER TABLE ' . $this->espoQuote($table) . ' ADD ' . $this->getForeignKeyDeclarationSQL($foreignKey); + + return $query; + } + + public function getIndexDeclarationSQL($name, Index $index) + { + $columns = $index->getQuotedColumns($this); + + if (count($columns) === 0) { + throw new \InvalidArgumentException("Incomplete definition. 'columns' required."); + } + + return $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $this->espoQuote($name) . ' (' + . $this->getIndexFieldDeclarationListSQL($columns) + . ')'; + } + + public function getCreateIndexSQL(Index $index, $table) + { + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + $name = $index->getQuotedName($this); + $columns = $index->getQuotedColumns($this); + + if (count($columns) == 0) { + throw new \InvalidArgumentException("Incomplete definition. 'columns' required."); + } + + if ($index->isPrimary()) { + return $this->getCreatePrimaryKeySQL($index, $table); + } + + $query = 'CREATE ' . $this->getCreateIndexSQLFlags($index) . 'INDEX ' . $name . ' ON ' . $this->espoQuote($table); + $query .= ' (' . $this->getIndexFieldDeclarationListSQL($columns) . ')'; + + return $query; + } + + public function getDropConstraintSQL($constraint, $table) + { + if ($constraint instanceof Constraint) { + $constraint = $constraint->getQuotedName($this); + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + return 'ALTER TABLE ' . $this->espoQuote($table) . ' DROP CONSTRAINT ' . $constraint; + } + + public function getDropForeignKeySQL($foreignKey, $table) + { + if ($foreignKey instanceof ForeignKeyConstraint) { + $foreignKey = $foreignKey->getQuotedName($this); + } + + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + return 'ALTER TABLE ' . $this->espoQuote($table) . ' DROP FOREIGN KEY ' . $foreignKey; + } + + public function getCreatePrimaryKeySQL(Index $index, $table) + { + if ($table instanceof Table) { + $table = $table->getQuotedName($this); + } + + return 'ALTER TABLE ' . $this->espoQuote($table) . ' ADD PRIMARY KEY (' . $this->getIndexFieldDeclarationListSQL($index->getQuotedColumns($this)) . ')'; + } + //end: ESPO +} \ No newline at end of file diff --git a/application/Espo/Core/Utils/Database/DBAL/Schema/Comparator.php b/application/Espo/Core/Utils/Database/DBAL/Schema/Comparator.php new file mode 100644 index 0000000000..aeb407a505 --- /dev/null +++ b/application/Espo/Core/Utils/Database/DBAL/Schema/Comparator.php @@ -0,0 +1,87 @@ +getType() != $column2->getType() ) { + + //espo: fix problem with executing query for custom types + $column1DbTypeName = method_exists($column1->getType(), 'getDbTypeName') ? $column1->getType()->getDbTypeName() : $column1->getType()->getName(); + $column2DbTypeName = method_exists($column2->getType(), 'getDbTypeName') ? $column2->getType()->getDbTypeName() : $column2->getType()->getName(); + + if (strtolower($column1DbTypeName) != strtolower($column2DbTypeName)) { + $changedProperties[] = 'type'; + } + //END: espo + } + + if ($column1->getNotnull() != $column2->getNotnull()) { + $changedProperties[] = 'notnull'; + } + + if ($column1->getDefault() != $column2->getDefault()) { + $changedProperties[] = 'default'; + } + + if ($column1->getUnsigned() != $column2->getUnsigned()) { + $changedProperties[] = 'unsigned'; + } + + if ($column1->getType() instanceof \Doctrine\DBAL\Types\StringType) { + // check if value of length is set at all, default value assumed otherwise. + $length1 = $column1->getLength() ?: 255; + $length2 = $column2->getLength() ?: 255; + if ($length1 != $length2) { + $changedProperties[] = 'length'; + } + + if ($column1->getFixed() != $column2->getFixed()) { + $changedProperties[] = 'fixed'; + } + } + + if ($column1->getType() instanceof \Doctrine\DBAL\Types\DecimalType) { + if (($column1->getPrecision()?:10) != ($column2->getPrecision()?:10)) { + $changedProperties[] = 'precision'; + } + if ($column1->getScale() != $column2->getScale()) { + $changedProperties[] = 'scale'; + } + } + + if ($column1->getAutoincrement() != $column2->getAutoincrement()) { + $changedProperties[] = 'autoincrement'; + } + + // only allow to delete comment if its set to '' not to null. + if ($column1->getComment() !== null && $column1->getComment() != $column2->getComment()) { + $changedProperties[] = 'comment'; + } + + $options1 = $column1->getCustomSchemaOptions(); + $options2 = $column2->getCustomSchemaOptions(); + + $commonKeys = array_keys(array_intersect_key($options1, $options2)); + + foreach ($commonKeys as $key) { + if ($options1[$key] !== $options2[$key]) { + $changedProperties[] = $key; + } + } + + $diffKeys = array_keys(array_diff_key($options1, $options2) + array_diff_key($options2, $options1)); + + $changedProperties = array_merge($changedProperties, $diffKeys); + + return $changedProperties; + } + +} \ No newline at end of file diff --git a/application/Espo/Core/Utils/Database/Schema/Converter.php b/application/Espo/Core/Utils/Database/Schema/Converter.php index f54a49ec27..147e05d8da 100644 --- a/application/Espo/Core/Utils/Database/Schema/Converter.php +++ b/application/Espo/Core/Utils/Database/Schema/Converter.php @@ -11,7 +11,7 @@ class Converter private $dbalSchema; private $fileManager; - private $customTablePath = 'application/Espo/Core/Utils/Database/Schema/customTables'; + private $customTablePath = 'application/Espo/Core/Utils/Database/Schema/CustomTables'; protected $typeList; @@ -148,18 +148,7 @@ class Converter if (!isset($tables[$tableName])) { //no needs to create the table if it already exists $tables[$tableName] = $this->prepareManyMany($entityName, $relationParams, $tables); } - break; - - case 'hasMany': - if (isset($relationParams['relationName'])) { - $tableName = $relationParams['relationName']; - - //check for duplication tables - if (!isset($tables[$tableName])) { //no needs to create the table if it already exists - $tables[$tableName] = $this->prepareManyMany($entityName, $relationParams, $tables); - } - } - break; + break; case 'belongsTo': $foreignEntity = $relationParams['entity']; diff --git a/application/Espo/Core/Utils/Database/Schema/customTables/Subscription.php b/application/Espo/Core/Utils/Database/Schema/CustomTables/Subscription.php similarity index 100% rename from application/Espo/Core/Utils/Database/Schema/customTables/Subscription.php rename to application/Espo/Core/Utils/Database/Schema/CustomTables/Subscription.php diff --git a/application/Espo/Core/Utils/Database/Schema/Schema.php b/application/Espo/Core/Utils/Database/Schema/Schema.php index 1fad3cbbd8..f3590a941a 100644 --- a/application/Espo/Core/Utils/Database/Schema/Schema.php +++ b/application/Espo/Core/Utils/Database/Schema/Schema.php @@ -18,6 +18,16 @@ class Schema private $connection; + protected $drivers = array( + 'mysqli' => '\Espo\Core\Utils\Database\DBAL\Driver\Mysqli\Driver', + 'pdo_mysql' => '\Espo\Core\Utils\Database\DBAL\Driver\PDOMySql\Driver', + ); + + protected $fieldTypePaths = array( + 'Espo/Core/Utils/Database/DBAL/FieldTypes', + 'Espo/Custom/Core/Utils/Database/DBAL/FieldTypes', + ); + public function __construct(\Espo\Core\Utils\Config $config, \Espo\Core\Utils\Metadata $metadata, \Espo\Core\Utils\File\Manager $fileManager) { @@ -25,7 +35,7 @@ class Schema $this->metadata = $metadata; $this->fileManager = $fileManager; - $this->comparator = new \Doctrine\DBAL\Schema\Comparator(); + $this->comparator = new \Espo\Core\Utils\Database\DBAL\Schema\Comparator(); $this->initFieldTypes(); $this->converter = new \Espo\Core\Utils\Database\Converter($this->metadata, $this->fileManager); @@ -72,6 +82,9 @@ class Schema $dbalConfig = new \Doctrine\DBAL\Configuration(); $connectionParams = (array) $this->getConfig()->get('database'); + + $connectionParams['driverClass'] = $this->drivers[ $connectionParams['driver'] ]; + unset($connectionParams['driver']); $this->connection = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $dbalConfig); @@ -81,12 +94,7 @@ class Schema protected function initFieldTypes() { - $typePaths = array( - 'Espo/Core/Utils/Database/FieldTypes', - 'Espo/Custom/Core/Utils/Database/FieldTypes', - ); - - foreach($typePaths as $path) { + foreach($this->fieldTypePaths as $path) { $typeList = $this->getFileManager()->getFileList('application/'.$path, false, '\.php$'); if ($typeList !== false) {