dbal ref
This commit is contained in:
@@ -1,109 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Driver\PDO\MySQL;
|
||||
|
||||
use Doctrine\DBAL\Driver\AbstractMySQLDriver;
|
||||
use Doctrine\DBAL\Driver\PDO\Connection;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
|
||||
use PDO;
|
||||
|
||||
// Espo: requires for the issue https://github.com/doctrine/dbal/issues/4496
|
||||
use Espo\Core\Utils\Database\DBAL\Schema\MySQLSchemaManager;
|
||||
use Doctrine\DBAL\Connection as MySQLDriverConnection;
|
||||
|
||||
|
||||
// End: espo
|
||||
|
||||
final class Driver extends AbstractMySQLDriver
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return Connection
|
||||
*/
|
||||
public function connect(array $params)
|
||||
{
|
||||
$driverOptions = $params['driverOptions'] ?? [];
|
||||
|
||||
if (!empty($params['persistent'])) {
|
||||
$driverOptions[PDO::ATTR_PERSISTENT] = true;
|
||||
}
|
||||
|
||||
$pdo = new PDO(
|
||||
$this->constructPdoDsn($params),
|
||||
$params['user'] ?? '',
|
||||
$params['password'] ?? '',
|
||||
$driverOptions
|
||||
);
|
||||
|
||||
return new Connection($pdo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the MySQL PDO DSN.
|
||||
*
|
||||
* @param mixed[] $params
|
||||
* @return string The DSN.
|
||||
*/
|
||||
protected function constructPdoDsn(array $params)
|
||||
{
|
||||
$dsn = 'mysql:';
|
||||
if (isset($params['host']) && $params['host'] !== '') {
|
||||
$dsn .= 'host=' . $params['host'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['port'])) {
|
||||
$dsn .= 'port=' . $params['port'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['dbname'])) {
|
||||
$dsn .= 'dbname=' . $params['dbname'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['unix_socket'])) {
|
||||
$dsn .= 'unix_socket=' . $params['unix_socket'] . ';';
|
||||
}
|
||||
|
||||
if (isset($params['charset'])) {
|
||||
$dsn .= 'charset=' . $params['charset'] . ';';
|
||||
}
|
||||
|
||||
return $dsn;
|
||||
}
|
||||
|
||||
// Espo: requires for the issue https://github.com/doctrine/dbal/issues/4496
|
||||
public function getSchemaManager(MySQLDriverConnection $conn, AbstractPlatform $platform)
|
||||
{
|
||||
assert($platform instanceof MySQLPlatform);
|
||||
return new MySQLSchemaManager($conn, $platform);
|
||||
}
|
||||
// End: espo
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Factories;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\VersionAwarePlatformDriver as Driver;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Database\DBAL\ConnectionFactory;
|
||||
use Espo\Core\Utils\Database\DBAL\Driver\PDO\MySQL\Driver as PDOMySQLDriver;
|
||||
use Espo\ORM\DatabaseParams;
|
||||
use Espo\ORM\PDO\Options as PdoOptions;
|
||||
|
||||
use PDO;
|
||||
use ReflectionClass;
|
||||
use RuntimeException;
|
||||
|
||||
class MysqlConnectionFactory implements ConnectionFactory
|
||||
{
|
||||
/** @var array<string, class-string<Driver>> */
|
||||
private $driverClassNameMap = [
|
||||
'mysqli' => 'Doctrine\\DBAL\\Driver\\Mysqli\\Driver', // @todo Revise usage.
|
||||
'pdo_mysql' => PDOMySQLDriver::class,
|
||||
];
|
||||
|
||||
/** @var array<string, class-string<AbstractPlatform>> */
|
||||
private $customPlatformClassNameMap = [
|
||||
'MariaDb1027Platform' => 'Espo\\Core\\Utils\\Database\\DBAL\\Platforms\\MariaDb1027Platform',
|
||||
'MySQL57Platform' => 'Espo\\Core\\Utils\\Database\\DBAL\\Platforms\\MySQL57Platform',
|
||||
'MySQL80Platform' => 'Espo\\Core\\Utils\\Database\\DBAL\\Platforms\\MySQL80Platform',
|
||||
'MySQLPlatform' => 'Espo\\Core\\Utils\\Database\\DBAL\\Platforms\\MySQLPlatform',
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
private PDO $pdo,
|
||||
private Config $config
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function create(DatabaseParams $databaseParams): Connection
|
||||
{
|
||||
$driver = $this->createDriver();
|
||||
|
||||
$version = $this->getFullDatabaseVersion() ?? '';
|
||||
$platform = $driver->createDatabasePlatformForVersion($version);
|
||||
|
||||
$params = [
|
||||
'platform' => $this->handlePlatform($platform),
|
||||
'host' => $databaseParams->getHost(),
|
||||
'port' => $databaseParams->getPort(),
|
||||
'dbname' => $databaseParams->getName(),
|
||||
'charset' => $databaseParams->getCharset(),
|
||||
'user' => $databaseParams->getUsername(),
|
||||
'password' => $databaseParams->getPassword(),
|
||||
'driverOptions' => PdoOptions::getOptionsFromDatabaseParams($databaseParams),
|
||||
];
|
||||
|
||||
return new Connection($params, $driver);
|
||||
}
|
||||
|
||||
private function createDriver(): Driver
|
||||
{
|
||||
$driverName = $this->config->get('database.driver') ?? 'pdo_mysql';
|
||||
|
||||
$driverClass = $this->driverClassNameMap[$driverName] ?? null;
|
||||
|
||||
if (!$driverClass) {
|
||||
throw new RuntimeException('Unknown database driver.');
|
||||
}
|
||||
|
||||
return new $driverClass();
|
||||
}
|
||||
|
||||
private function handlePlatform(AbstractPlatform $platform): AbstractPlatform
|
||||
{
|
||||
$reflect = new ReflectionClass($platform);
|
||||
|
||||
$platformClass = $reflect->getShortName();
|
||||
|
||||
if (isset($this->customPlatformClassNameMap[$platformClass])) {
|
||||
$className = $this->customPlatformClassNameMap[$platformClass];
|
||||
|
||||
return new $className();
|
||||
}
|
||||
|
||||
return $platform;
|
||||
}
|
||||
|
||||
private function getFullDatabaseVersion(): ?string
|
||||
{
|
||||
$sth = $this->pdo->prepare("select version()");
|
||||
|
||||
$sth->execute();
|
||||
|
||||
/** @var string|null|false $result */
|
||||
$result = $sth->fetchColumn();
|
||||
|
||||
if ($result === false || $result === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Platforms;
|
||||
|
||||
use Doctrine\DBAL\{
|
||||
Types\Types,
|
||||
Platforms\Keywords\MariaDb102Keywords,
|
||||
};
|
||||
|
||||
class MariaDb1027Platform extends MySQLPlatform
|
||||
{
|
||||
public function getJsonTypeDeclarationSQL(array $column): string
|
||||
{
|
||||
return 'LONGTEXT';
|
||||
}
|
||||
|
||||
protected function getReservedKeywordsClass(): string
|
||||
{
|
||||
return MariaDb102Keywords::class;
|
||||
}
|
||||
|
||||
protected function initializeDoctrineTypeMappings(): void
|
||||
{
|
||||
parent::initializeDoctrineTypeMappings();
|
||||
|
||||
$this->doctrineTypeMapping['json'] = Types::JSON;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Platforms;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQL57Platform as OriginalMySQL57Platform;
|
||||
use Espo\Core\Utils\Database\DBAL\Traits\Platforms\MySQLPlatform as MySQLPlatformTrait;
|
||||
|
||||
class MySQL57Platform extends OriginalMySQL57Platform
|
||||
{
|
||||
use MySQLPlatformTrait;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Platforms;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQL80Platform as OriginalMySQL80Platform;
|
||||
use Espo\Core\Utils\Database\DBAL\Traits\Platforms\MySQLPlatform as MySQLPlatformTrait;
|
||||
|
||||
class MySQL80Platform extends OriginalMySQL80Platform
|
||||
{
|
||||
use MySQLPlatformTrait;
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Platforms;
|
||||
|
||||
use Doctrine\DBAL\Platforms\MySQLPlatform as OriginalMySQLPlatform;
|
||||
use Espo\Core\Utils\Database\DBAL\Traits\Platforms\MySQLPlatform as MySQLPlatformTrait;
|
||||
|
||||
class MySQLPlatform extends OriginalMySQLPlatform
|
||||
{
|
||||
use MySQLPlatformTrait;
|
||||
}
|
||||
@@ -1,353 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Schema;
|
||||
|
||||
use Espo\Core\Utils\Database\DBAL\Traits\Schema\Comparator as ComparatorTrait;
|
||||
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\ColumnDiff;
|
||||
use Doctrine\DBAL\Schema\Comparator as OriginalComparator;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\DBAL\Schema\TableDiff;
|
||||
use Doctrine\DBAL\Types;
|
||||
|
||||
class Comparator extends OriginalComparator
|
||||
{
|
||||
// Espo
|
||||
use ComparatorTrait;
|
||||
// Espo: end
|
||||
|
||||
public function diffColumn(Column $column1, Column $column2)
|
||||
{
|
||||
$properties1 = $column1->toArray();
|
||||
$properties2 = $column2->toArray();
|
||||
|
||||
$changedProperties = [];
|
||||
|
||||
if (get_class($properties1['type']) !== get_class($properties2['type'])) {
|
||||
// Espo
|
||||
$this->espoFixTypeDiff($changedProperties, $column1, $column2);
|
||||
// Espo: end
|
||||
}
|
||||
|
||||
foreach (['notnull', 'unsigned', 'autoincrement'] as $property) {
|
||||
if ($properties1[$property] === $properties2[$property]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$changedProperties[] = $property;
|
||||
}
|
||||
|
||||
// Null values need to be checked additionally as they tell whether to create or drop a default value.
|
||||
// null != 0, null != false, null != '' etc. This affects platform's table alteration SQL generation.
|
||||
if (
|
||||
($properties1['default'] === null) !== ($properties2['default'] === null)
|
||||
|| $properties1['default'] != $properties2['default']
|
||||
) {
|
||||
$changedProperties[] = 'default';
|
||||
}
|
||||
|
||||
if (
|
||||
($properties1['type'] instanceof Types\StringType && ! $properties1['type'] instanceof Types\GuidType) ||
|
||||
$properties1['type'] instanceof Types\BinaryType
|
||||
) {
|
||||
// check if value of length is set at all, default value assumed otherwise.
|
||||
$length1 = $properties1['length'] ?? 255;
|
||||
$length2 = $properties2['length'] ?? 255;
|
||||
|
||||
// Espo: column length can only be increased
|
||||
/*if ($length1 !== $length2) {
|
||||
$changedProperties[] = 'length';
|
||||
}*/
|
||||
if ($length2 > $length1) {
|
||||
$changedProperties[] = 'length';
|
||||
}
|
||||
if ($length2 < $length1) {
|
||||
$column2->setLength($length1);
|
||||
}
|
||||
// Espo: end
|
||||
|
||||
if ($properties1['fixed'] !== $properties2['fixed']) {
|
||||
$changedProperties[] = 'fixed';
|
||||
}
|
||||
} elseif ($properties1['type'] instanceof Types\DecimalType) {
|
||||
if (($properties1['precision'] ?? 10) !== ($properties2['precision'] ?? 10)) {
|
||||
$changedProperties[] = 'precision';
|
||||
}
|
||||
|
||||
if ($properties1['scale'] !== $properties2['scale']) {
|
||||
$changedProperties[] = 'scale';
|
||||
}
|
||||
}
|
||||
|
||||
// A null value and an empty string are actually equal for a comment so they should not trigger a change.
|
||||
if (
|
||||
$properties1['comment'] !== $properties2['comment'] &&
|
||||
! ($properties1['comment'] === null && $properties2['comment'] === '') &&
|
||||
! ($properties2['comment'] === null && $properties1['comment'] === '')
|
||||
) {
|
||||
$changedProperties[] = 'comment';
|
||||
}
|
||||
|
||||
$customOptions1 = $column1->getCustomSchemaOptions();
|
||||
$customOptions2 = $column2->getCustomSchemaOptions();
|
||||
|
||||
foreach (array_merge(array_keys($customOptions1), array_keys($customOptions2)) as $key) {
|
||||
if (! array_key_exists($key, $properties1) || ! array_key_exists($key, $properties2)) {
|
||||
$changedProperties[] = $key;
|
||||
} elseif ($properties1[$key] !== $properties2[$key]) {
|
||||
$changedProperties[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
$platformOptions1 = $column1->getPlatformOptions();
|
||||
$platformOptions2 = $column2->getPlatformOptions();
|
||||
|
||||
foreach (array_keys(array_intersect_key($platformOptions1, $platformOptions2)) as $key) {
|
||||
if ($properties1[$key] === $properties2[$key]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Espo: skip collation changes
|
||||
if ($key == 'collation') {
|
||||
$column2->setPlatformOption('collation', $platformOptions1['collation']);
|
||||
continue;
|
||||
}
|
||||
// Espo: end
|
||||
|
||||
$changedProperties[] = $key;
|
||||
}
|
||||
|
||||
return array_unique($changedProperties);
|
||||
}
|
||||
|
||||
public function diffTable(Table $fromTable, Table $toTable)
|
||||
{
|
||||
$changes = 0;
|
||||
$tableDifferences = new TableDiff($fromTable->getName());
|
||||
$tableDifferences->fromTable = $fromTable;
|
||||
|
||||
$fromTableColumns = $fromTable->getColumns();
|
||||
$toTableColumns = $toTable->getColumns();
|
||||
|
||||
/* See if all the columns in "from" table exist in "to" table */
|
||||
foreach ($toTableColumns as $columnName => $column) {
|
||||
if ($fromTable->hasColumn($columnName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tableDifferences->addedColumns[$columnName] = $column;
|
||||
$changes++;
|
||||
}
|
||||
|
||||
/* See if there are any removed columns in "to" table */
|
||||
foreach ($fromTableColumns as $columnName => $column) {
|
||||
// See if column is removed in "to" table.
|
||||
if (! $toTable->hasColumn($columnName)) {
|
||||
$tableDifferences->removedColumns[$columnName] = $column;
|
||||
$changes++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// See if column has changed properties in "to" table.
|
||||
$changedProperties = $this->diffColumn($column, $toTable->getColumn($columnName));
|
||||
|
||||
if (count($changedProperties) === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columnDiff = new ColumnDiff($column->getName(), $toTable->getColumn($columnName), $changedProperties);
|
||||
|
||||
$columnDiff->fromColumn = $column;
|
||||
$tableDifferences->changedColumns[$column->getName()] = $columnDiff;
|
||||
$changes++;
|
||||
}
|
||||
|
||||
$this->detectColumnRenamings($tableDifferences);
|
||||
|
||||
$fromTableIndexes = $fromTable->getIndexes();
|
||||
$toTableIndexes = $toTable->getIndexes();
|
||||
|
||||
/* See if all the indexes in "from" table exist in "to" table */
|
||||
foreach ($toTableIndexes as $indexName => $index) {
|
||||
if (($index->isPrimary() && $fromTable->hasPrimaryKey()) || $fromTable->hasIndex($indexName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tableDifferences->addedIndexes[$indexName] = $index;
|
||||
$changes++;
|
||||
}
|
||||
|
||||
/* See if there are any removed indexes in "to" table */
|
||||
foreach ($fromTableIndexes as $indexName => $index) {
|
||||
// See if index is removed in "to" table.
|
||||
if (
|
||||
($index->isPrimary() && ! $toTable->hasPrimaryKey()) ||
|
||||
! $index->isPrimary() && ! $toTable->hasIndex($indexName)
|
||||
) {
|
||||
$tableDifferences->removedIndexes[$indexName] = $index;
|
||||
$changes++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// See if index has changed in "to" table.
|
||||
$toTableIndex = $index->isPrimary() ? $toTable->getPrimaryKey() : $toTable->getIndex($indexName);
|
||||
assert($toTableIndex instanceof Index);
|
||||
|
||||
if (! $this->diffIndex($index, $toTableIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tableDifferences->changedIndexes[$indexName] = $toTableIndex;
|
||||
$changes++;
|
||||
}
|
||||
|
||||
$this->detectIndexRenamings($tableDifferences);
|
||||
|
||||
$fromForeignKeys = $fromTable->getForeignKeys();
|
||||
$toForeignKeys = $toTable->getForeignKeys();
|
||||
|
||||
foreach ($fromForeignKeys as $fromKey => $fromConstraint) {
|
||||
foreach ($toForeignKeys as $toKey => $toConstraint) {
|
||||
if ($this->diffForeignKey($fromConstraint, $toConstraint) === false) {
|
||||
unset($fromForeignKeys[$fromKey], $toForeignKeys[$toKey]);
|
||||
} else {
|
||||
if (strtolower($fromConstraint->getName()) === strtolower($toConstraint->getName())) {
|
||||
$tableDifferences->changedForeignKeys[] = $toConstraint;
|
||||
$changes++;
|
||||
unset($fromForeignKeys[$fromKey], $toForeignKeys[$toKey]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($fromForeignKeys as $fromConstraint) {
|
||||
$tableDifferences->removedForeignKeys[] = $fromConstraint;
|
||||
$changes++;
|
||||
}
|
||||
|
||||
foreach ($toForeignKeys as $toConstraint) {
|
||||
$tableDifferences->addedForeignKeys[] = $toConstraint;
|
||||
$changes++;
|
||||
}
|
||||
|
||||
return $changes > 0 ? $tableDifferences : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find columns that only changed their name, rename operations maybe cheaper than add/drop
|
||||
* however ambiguities between different possibilities should not lead to renaming at all.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function detectColumnRenamings(TableDiff $tableDifferences)
|
||||
{
|
||||
$renameCandidates = [];
|
||||
foreach ($tableDifferences->addedColumns as $addedColumnName => $addedColumn) {
|
||||
foreach ($tableDifferences->removedColumns as $removedColumn) {
|
||||
if (count($this->diffColumn($addedColumn, $removedColumn)) !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$renameCandidates[$addedColumn->getName()][] = [$removedColumn, $addedColumn, $addedColumnName];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($renameCandidates as $candidateColumns) {
|
||||
if (count($candidateColumns) !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$removedColumn, $addedColumn] = $candidateColumns[0];
|
||||
$removedColumnName = strtolower($removedColumn->getName());
|
||||
$addedColumnName = strtolower($addedColumn->getName());
|
||||
|
||||
if (isset($tableDifferences->renamedColumns[$removedColumnName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tableDifferences->renamedColumns[$removedColumnName] = $addedColumn;
|
||||
unset(
|
||||
$tableDifferences->addedColumns[$addedColumnName],
|
||||
$tableDifferences->removedColumns[$removedColumnName]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to find indexes that only changed their name, rename operations maybe cheaper than add/drop
|
||||
* however ambiguities between different possibilities should not lead to renaming at all.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function detectIndexRenamings(TableDiff $tableDifferences)
|
||||
{
|
||||
$renameCandidates = [];
|
||||
|
||||
// Gather possible rename candidates by comparing each added and removed index based on semantics.
|
||||
foreach ($tableDifferences->addedIndexes as $addedIndexName => $addedIndex) {
|
||||
foreach ($tableDifferences->removedIndexes as $removedIndex) {
|
||||
if ($this->diffIndex($addedIndex, $removedIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$renameCandidates[$addedIndex->getName()][] = [$removedIndex, $addedIndex, $addedIndexName];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($renameCandidates as $candidateIndexes) {
|
||||
// If the current rename candidate contains exactly one semantically equal index,
|
||||
// we can safely rename it.
|
||||
// Otherwise it is unclear if a rename action is really intended,
|
||||
// therefore we let those ambiguous indexes be added/dropped.
|
||||
if (count($candidateIndexes) !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$removedIndex, $addedIndex] = $candidateIndexes[0];
|
||||
|
||||
$removedIndexName = strtolower($removedIndex->getName());
|
||||
$addedIndexName = strtolower($addedIndex->getName());
|
||||
|
||||
if (isset($tableDifferences->renamedIndexes[$removedIndexName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Espo: skip index renaming
|
||||
//$tableDifferences->renamedIndexes[$removedIndexName] = $addedIndex;
|
||||
// Espo: end
|
||||
unset(
|
||||
$tableDifferences->addedIndexes[$addedIndexName],
|
||||
$tableDifferences->removedIndexes[$removedIndexName]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Schema\MySQLSchemaManager as Original;
|
||||
|
||||
// Espo: requires for the issue https://github.com/doctrine/dbal/issues/4496
|
||||
|
||||
//use Doctrine\DBAL\Platforms\MariaDb1027Platform;
|
||||
//use Doctrine\DBAL\Platforms\MySQLPlatform;
|
||||
use Espo\Core\Utils\Database\DBAL\Platforms\MariaDb1027Platform;
|
||||
use Espo\Core\Utils\Database\DBAL\Platforms\MySQLPlatform;
|
||||
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
// Espo: end
|
||||
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
use function array_change_key_case;
|
||||
use function array_shift;
|
||||
use function array_values;
|
||||
use function assert;
|
||||
use function explode;
|
||||
use function is_string;
|
||||
use function preg_match;
|
||||
use function strpos;
|
||||
use function strtok;
|
||||
use function strtolower;
|
||||
use function strtr;
|
||||
|
||||
use const CASE_LOWER;
|
||||
|
||||
class MySQLSchemaManager extends Original
|
||||
{
|
||||
private const MARIADB_ESCAPE_SEQUENCES = [
|
||||
'\\0' => "\0",
|
||||
"\\'" => "'",
|
||||
'\\"' => '"',
|
||||
'\\b' => "\b",
|
||||
'\\n' => "\n",
|
||||
'\\r' => "\r",
|
||||
'\\t' => "\t",
|
||||
'\\Z' => "\x1a",
|
||||
'\\\\' => '\\',
|
||||
'\\%' => '%',
|
||||
'\\_' => '_',
|
||||
|
||||
// Internally, MariaDB escapes single quotes using the standard syntax
|
||||
"''" => "'",
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function _getPortableTableColumnDefinition($tableColumn)
|
||||
{
|
||||
$tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
|
||||
|
||||
$dbType = strtolower($tableColumn['type']);
|
||||
$dbType = strtok($dbType, '(), ');
|
||||
assert(is_string($dbType));
|
||||
|
||||
$length = $tableColumn['length'] ?? strtok('(), ');
|
||||
|
||||
$fixed = null;
|
||||
|
||||
if (! isset($tableColumn['name'])) {
|
||||
$tableColumn['name'] = '';
|
||||
}
|
||||
|
||||
$scale = null;
|
||||
$precision = null;
|
||||
|
||||
$type = $this->_platform->getDoctrineTypeMapping($dbType);
|
||||
|
||||
// In cases where not connected to a database DESCRIBE $table does not return 'Comment'
|
||||
if (isset($tableColumn['comment'])) {
|
||||
$type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
$tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
}
|
||||
|
||||
switch ($dbType) {
|
||||
case 'char':
|
||||
case 'binary':
|
||||
$fixed = true;
|
||||
break;
|
||||
|
||||
case 'float':
|
||||
case 'double':
|
||||
case 'real':
|
||||
case 'numeric':
|
||||
case 'decimal':
|
||||
if (
|
||||
preg_match(
|
||||
'([A-Za-z]+\(([0-9]+),([0-9]+)\))',
|
||||
$tableColumn['type'],
|
||||
$match
|
||||
) === 1
|
||||
) {
|
||||
$precision = $match[1];
|
||||
$scale = $match[2];
|
||||
$length = null;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'tinytext':
|
||||
$length = MySQLPlatform::LENGTH_LIMIT_TINYTEXT;
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
$length = MySQLPlatform::LENGTH_LIMIT_TEXT;
|
||||
break;
|
||||
|
||||
case 'mediumtext':
|
||||
$length = MySQLPlatform::LENGTH_LIMIT_MEDIUMTEXT;
|
||||
break;
|
||||
|
||||
case 'tinyblob':
|
||||
$length = MySQLPlatform::LENGTH_LIMIT_TINYBLOB;
|
||||
break;
|
||||
|
||||
case 'blob':
|
||||
$length = MySQLPlatform::LENGTH_LIMIT_BLOB;
|
||||
break;
|
||||
|
||||
case 'mediumblob':
|
||||
$length = MySQLPlatform::LENGTH_LIMIT_MEDIUMBLOB;
|
||||
break;
|
||||
|
||||
case 'tinyint':
|
||||
case 'smallint':
|
||||
case 'mediumint':
|
||||
case 'int':
|
||||
case 'integer':
|
||||
case 'bigint':
|
||||
case 'year':
|
||||
$length = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->_platform instanceof MariaDb1027Platform) {
|
||||
$columnDefault = $this->getMariaDb1027ColumnDefault($this->_platform, $tableColumn['default']);
|
||||
} else {
|
||||
$columnDefault = $tableColumn['default'];
|
||||
}
|
||||
|
||||
$options = [
|
||||
'length' => $length !== null ? (int) $length : null,
|
||||
'unsigned' => strpos($tableColumn['type'], 'unsigned') !== false,
|
||||
'fixed' => (bool) $fixed,
|
||||
'default' => $columnDefault,
|
||||
'notnull' => $tableColumn['null'] !== 'YES',
|
||||
'scale' => null,
|
||||
'precision' => null,
|
||||
'autoincrement' => strpos($tableColumn['extra'], 'auto_increment') !== false,
|
||||
'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== ''
|
||||
? $tableColumn['comment']
|
||||
: null,
|
||||
];
|
||||
|
||||
if ($scale !== null && $precision !== null) {
|
||||
$options['scale'] = (int) $scale;
|
||||
$options['precision'] = (int) $precision;
|
||||
}
|
||||
|
||||
$column = new Column($tableColumn['field'], Type::getType($type), $options);
|
||||
|
||||
if (isset($tableColumn['characterset'])) {
|
||||
$column->setPlatformOption('charset', $tableColumn['characterset']);
|
||||
}
|
||||
|
||||
if (isset($tableColumn['collation'])) {
|
||||
$column->setPlatformOption('collation', $tableColumn['collation']);
|
||||
}
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
private function getMariaDb1027ColumnDefault(MariaDb1027Platform $platform, ?string $columnDefault): ?string
|
||||
{
|
||||
if ($columnDefault === 'NULL' || $columnDefault === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('/^\'(.*)\'$/', $columnDefault, $matches) === 1) {
|
||||
return strtr($matches[1], self::MARIADB_ESCAPE_SEQUENCES);
|
||||
}
|
||||
|
||||
switch ($columnDefault) {
|
||||
case 'current_timestamp()':
|
||||
return $platform->getCurrentTimestampSQL();
|
||||
|
||||
case 'curdate()':
|
||||
return $platform->getCurrentDateSQL();
|
||||
|
||||
case 'curtime()':
|
||||
return $platform->getCurrentTimeSQL();
|
||||
}
|
||||
|
||||
return $columnDefault;
|
||||
}
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Traits\Platforms;
|
||||
|
||||
use Doctrine\DBAL\Types\BlobType;
|
||||
use Doctrine\DBAL\Types\TextType;
|
||||
|
||||
use Doctrine\DBAL\Schema\{
|
||||
TableDiff,
|
||||
ColumnDiff,
|
||||
Column,
|
||||
Identifier,
|
||||
};
|
||||
|
||||
trait MySQLPlatform
|
||||
{
|
||||
public function getAlterTableSQL(TableDiff $diff)
|
||||
{
|
||||
$columnSql = [];
|
||||
$queryParts = [];
|
||||
$newName = $diff->getNewName();
|
||||
|
||||
if ($newName !== false) {
|
||||
$queryParts[] = 'RENAME TO ' . $newName->getQuotedName($this);
|
||||
}
|
||||
|
||||
foreach ($diff->renamedColumns as $oldColumnName => $column) {
|
||||
if ($this->onSchemaAlterTableRenameColumn($oldColumnName, $column, $diff, $columnSql)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Espo: handle remained autoincrement column
|
||||
if ($column->getAutoincrement()) {
|
||||
$oldColumnOptions = array_diff_key($column->toArray(), array_flip(['name', 'type', 'collation']));
|
||||
$diff->removedColumns[$oldColumnName] = new Column($oldColumnName, $column->getType(), $oldColumnOptions);
|
||||
|
||||
$columnName = $column->getQuotedName($this);
|
||||
$diff->addedColumns[$columnName] = $column;
|
||||
continue;
|
||||
}
|
||||
// Espo: end
|
||||
|
||||
$oldColumnName = new Identifier($oldColumnName);
|
||||
$columnArray = $column->toArray();
|
||||
$columnArray['comment'] = $this->getColumnComment($column);
|
||||
|
||||
// Espo: do not rename the column
|
||||
/* $queryParts[] = 'CHANGE ' . $oldColumnName->getQuotedName($this) . ' '
|
||||
$this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray); */
|
||||
$queryParts[] = 'ADD '
|
||||
. $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray);
|
||||
// Espo: end
|
||||
}
|
||||
|
||||
foreach ($diff->addedColumns as $column) {
|
||||
if ($this->onSchemaAlterTableAddColumn($column, $diff, $columnSql)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$columnArray = array_merge($column->toArray(), [
|
||||
'comment' => $this->getColumnComment($column),
|
||||
]);
|
||||
|
||||
// Espo: Unable to create autoindex column in existing table fix
|
||||
if ($column->getAutoincrement()) {
|
||||
$columnArray['unique'] = true;
|
||||
|
||||
$columnName = $column->getQuotedName($this);
|
||||
|
||||
foreach ($diff->addedIndexes as $indexName => $index) {
|
||||
if ($index->getColumns() === [$columnName]) {
|
||||
$columnArray['uniqueDeclaration'] = $index->getName() . "(" . $columnName . ")";
|
||||
unset($diff->addedIndexes[$indexName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Espo: end
|
||||
|
||||
$queryParts[] = 'ADD ' . $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray);
|
||||
}
|
||||
|
||||
// Espo: remove autoincrement column
|
||||
$autoincrementRemovedIndexes = [];
|
||||
// Espo: end
|
||||
|
||||
foreach ($diff->removedColumns as $column) {
|
||||
if ($this->onSchemaAlterTableRemoveColumn($column, $diff, $columnSql)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Espo: remove autoincrement option
|
||||
if ($column->getAutoincrement()) {
|
||||
|
||||
$columnName = $column->getQuotedName($this);
|
||||
|
||||
$changedColumn = clone $column;
|
||||
$changedColumn->setNotNull(false);
|
||||
$changedColumn->setAutoincrement(false);
|
||||
|
||||
$changedProperties = array(
|
||||
'notnull',
|
||||
'autoincrement',
|
||||
);
|
||||
|
||||
$diff->changedColumns[$columnName] = new ColumnDiff($columnName, $changedColumn, $changedProperties, $column);
|
||||
|
||||
foreach ($diff->removedIndexes as $indexName => $index) {
|
||||
if ($index->getColumns() === [$columnName]) {
|
||||
$autoincrementRemovedIndexes[$indexName] = $index;
|
||||
unset($diff->removedIndexes[$indexName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Espo: end
|
||||
|
||||
// Espo: No need to delete the column
|
||||
//$queryParts[] = 'DROP ' . $column->getQuotedName($this);
|
||||
// Espo: end
|
||||
}
|
||||
|
||||
foreach ($diff->changedColumns as $columnDiff) {
|
||||
if ($this->onSchemaAlterTableChangeColumn($columnDiff, $diff, $columnSql)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$column = $columnDiff->column;
|
||||
$columnArray = $column->toArray();
|
||||
|
||||
// Don't propagate default value changes for unsupported column types.
|
||||
if (
|
||||
$columnDiff->hasChanged('default') &&
|
||||
count($columnDiff->changedProperties) === 1 &&
|
||||
($columnArray['type'] instanceof TextType || $columnArray['type'] instanceof BlobType)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Espo: Unable to create autoindex column in existing table fix
|
||||
if ($column->getAutoincrement()) {
|
||||
$columnArray['unique'] = true;
|
||||
|
||||
$columnName = $column->getQuotedName($this);
|
||||
|
||||
foreach ($diff->addedIndexes as $indexName => $index) {
|
||||
if ($index->getColumns() === [$columnName]) {
|
||||
$columnArray['uniqueDeclaration'] = $index->getName() . "(" . $columnName . ")";
|
||||
unset($diff->addedIndexes[$indexName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Espo: end
|
||||
|
||||
$columnArray['comment'] = $this->getColumnComment($column);
|
||||
$queryParts[] = 'CHANGE ' . ($columnDiff->getOldColumnName()->getQuotedName($this)) . ' '
|
||||
. $this->getColumnDeclarationSQL($column->getQuotedName($this), $columnArray);
|
||||
}
|
||||
|
||||
if (isset($diff->addedIndexes['primary'])) {
|
||||
$keyColumns = array_unique(array_values($diff->addedIndexes['primary']->getColumns()));
|
||||
$queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')';
|
||||
unset($diff->addedIndexes['primary']);
|
||||
} elseif (isset($diff->changedIndexes['primary'])) {
|
||||
// Necessary in case the new primary key includes a new auto_increment column
|
||||
foreach ($diff->changedIndexes['primary']->getColumns() as $columnName) {
|
||||
if (isset($diff->addedColumns[$columnName]) && $diff->addedColumns[$columnName]->getAutoincrement()) {
|
||||
$keyColumns = array_unique(array_values($diff->changedIndexes['primary']->getColumns()));
|
||||
$queryParts[] = 'DROP PRIMARY KEY';
|
||||
$queryParts[] = 'ADD PRIMARY KEY (' . implode(', ', $keyColumns) . ')';
|
||||
unset($diff->changedIndexes['primary']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sql = [];
|
||||
$tableSql = [];
|
||||
|
||||
if (! $this->onSchemaAlterTable($diff, $tableSql)) {
|
||||
if (count($queryParts) > 0) {
|
||||
$sql[] = 'ALTER TABLE ' . $diff->getName($this)->getQuotedName($this) . ' '
|
||||
. implode(', ', $queryParts);
|
||||
}
|
||||
|
||||
// Espo: remove autoincrement column
|
||||
if (!empty($autoincrementRemovedIndexes)) {
|
||||
$tableName = $diff->getName($this)->getQuotedName($this);
|
||||
|
||||
foreach ($autoincrementRemovedIndexes as $index) {
|
||||
$sql[] = $this->getDropIndexSQL($index, $tableName);
|
||||
}
|
||||
}
|
||||
// Espo: end
|
||||
|
||||
$sql = array_merge(
|
||||
$this->getPreAlterTableIndexForeignKeySQL($diff),
|
||||
$sql,
|
||||
$this->getPostAlterTableIndexForeignKeySQL($diff)
|
||||
);
|
||||
}
|
||||
|
||||
return array_merge($sql, $tableSql, $columnSql);
|
||||
}
|
||||
|
||||
public function getClobTypeDeclarationSQL(array $column)
|
||||
{
|
||||
if (! empty($column['length']) && is_numeric($column['length'])) {
|
||||
$length = $column['length'];
|
||||
|
||||
if ($length <= static::LENGTH_LIMIT_TINYTEXT) {
|
||||
return 'TINYTEXT';
|
||||
}
|
||||
|
||||
if ($length <= static::LENGTH_LIMIT_TEXT) {
|
||||
return 'TEXT';
|
||||
}
|
||||
|
||||
if ($length > static::LENGTH_LIMIT_MEDIUMTEXT) {
|
||||
return 'LONGTEXT';
|
||||
}
|
||||
}
|
||||
|
||||
return 'MEDIUMTEXT';
|
||||
}
|
||||
|
||||
protected function _getCreateTableSQL($name, array $columns, array $options = [])
|
||||
{
|
||||
if (!isset($options['charset'])) {
|
||||
$options['charset'] = 'utf8mb4';
|
||||
}
|
||||
|
||||
return parent::_getCreateTableSQL($name, $columns, $options);
|
||||
}
|
||||
|
||||
public function getColumnDeclarationSQL($name, array $column)
|
||||
{
|
||||
if (isset($column['columnDefinition'])) {
|
||||
$declaration = $this->getCustomTypeDeclarationSQL($column);
|
||||
} else {
|
||||
$default = $this->getDefaultValueDeclarationSQL($column);
|
||||
|
||||
$charset = ! empty($column['charset']) ?
|
||||
' ' . $this->getColumnCharsetDeclarationSQL($column['charset']) : '';
|
||||
|
||||
$collation = ! empty($column['collation']) ?
|
||||
' ' . $this->getColumnCollationDeclarationSQL($column['collation']) : '';
|
||||
|
||||
$notnull = ! empty($column['notnull']) ? ' NOT NULL' : '';
|
||||
|
||||
$unique = ! empty($column['unique']) ?
|
||||
' ' . $this->getUniqueFieldDeclarationSQL() : '';
|
||||
|
||||
$check = ! empty($column['check']) ? ' ' . $column['check'] : '';
|
||||
|
||||
$typeDecl = $column['type']->getSQLDeclaration($column, $this);
|
||||
$declaration = $typeDecl . $charset . $default . $notnull . $unique . $check . $collation;
|
||||
|
||||
// Espo: Unable to create autoindex column in existing table fix
|
||||
if (!empty($column['uniqueDeclaration'])) {
|
||||
$declaration = $typeDecl . $charset . $default . $notnull . $check . $collation
|
||||
. ", ADD". $unique . " " . $column['uniqueDeclaration'];
|
||||
}
|
||||
// Espo: end
|
||||
|
||||
if ($this->supportsInlineColumnComments() && isset($column['comment']) && $column['comment'] !== '') {
|
||||
$declaration .= ' ' . $this->getInlineColumnCommentSQL($column['comment']);
|
||||
}
|
||||
}
|
||||
|
||||
return $name . ' ' . $declaration;
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Traits\Schema;
|
||||
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Types\TextType;
|
||||
|
||||
trait Comparator
|
||||
{
|
||||
/**
|
||||
* Fix problem with executing query for custom types.
|
||||
*
|
||||
* @param string[] $changedProperties
|
||||
* @return void
|
||||
*/
|
||||
protected function espoFixTypeDiff(array &$changedProperties, Column $column1, Column $column2)
|
||||
{
|
||||
$column1DbType = $this->getColumnDbType($column1);
|
||||
$column2DbType = $this->getColumnDbType($column2);
|
||||
|
||||
if ($column1DbType == $column2DbType) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $column1->getType() instanceof TextType) {
|
||||
$changedProperties[] = 'type';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$column1DbLength = $this->getColumnDbLength($column1);
|
||||
$column2DbLength = $this->getColumnDbLength($column2);
|
||||
|
||||
if (
|
||||
$column1DbLength && $column2DbLength &&
|
||||
$column2DbLength > $column1DbLength
|
||||
) {
|
||||
$changedProperties[] = 'type';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function getColumnDbType(Column $column)
|
||||
{
|
||||
$dbType = $column->getType()->getName();
|
||||
|
||||
// Here was db type obtained by dbal type.
|
||||
// Can't obtain it from Connection as the mapping is not public.
|
||||
|
||||
return strtoupper($dbType);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ?int
|
||||
*/
|
||||
private function getColumnDbLength(Column $column)
|
||||
{
|
||||
$dbType = $this->getColumnDbType($column);
|
||||
|
||||
switch ($dbType) {
|
||||
case 'LONGTEXT':
|
||||
return 4294967295;
|
||||
}
|
||||
|
||||
$constName = '\\Doctrine\\DBAL\\Platforms\\MySQLPlatform::LENGTH_LIMIT_' . $dbType;
|
||||
|
||||
if (defined($constName)) {
|
||||
return constant($constName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL;
|
||||
namespace Espo\Core\Utils\Database\Dbal;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Espo\ORM\DatabaseParams;
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL;
|
||||
namespace Espo\Core\Utils\Database\Dbal;
|
||||
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\InjectableFactory;
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\Dbal\Factories;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver\PDO\MySQL\Driver as PDOMySQLDriver;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
|
||||
use Espo\Core\Utils\Database\Dbal\ConnectionFactory;
|
||||
use Espo\ORM\DatabaseParams;
|
||||
use Espo\ORM\PDO\Options as PdoOptions;
|
||||
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
class MysqlConnectionFactory implements ConnectionFactory
|
||||
{
|
||||
public function __construct(
|
||||
private PDO $pdo
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @throws DBALException
|
||||
*/
|
||||
public function create(DatabaseParams $databaseParams): Connection
|
||||
{
|
||||
$driver = new PDOMySQLDriver();
|
||||
|
||||
if (!$databaseParams->getHost() || !$databaseParams->getName()) {
|
||||
throw new RuntimeException("No required database params.");
|
||||
}
|
||||
|
||||
$params = [
|
||||
'pdo' => $this->pdo,
|
||||
'host' => $databaseParams->getHost(),
|
||||
'dbname' => $databaseParams->getName(),
|
||||
'driverOptions' => PdoOptions::getOptionsFromDatabaseParams($databaseParams),
|
||||
];
|
||||
|
||||
if ($databaseParams->getPort() !== null) {
|
||||
$params['port'] = $databaseParams->getPort();
|
||||
}
|
||||
|
||||
if ($databaseParams->getUsername() !== null) {
|
||||
$params['user'] = $databaseParams->getUsername();
|
||||
}
|
||||
|
||||
if ($databaseParams->getPassword() !== null) {
|
||||
$params['password'] = $databaseParams->getPassword();
|
||||
}
|
||||
|
||||
if ($databaseParams->getCharset() !== null) {
|
||||
$params['charset'] = $databaseParams->getCharset();
|
||||
}
|
||||
|
||||
return new Connection($params, $driver);
|
||||
}
|
||||
}
|
||||
+23
-35
@@ -27,17 +27,17 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Factories;
|
||||
namespace Espo\Core\Utils\Database\Dbal\Factories;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Driver\PDO\PgSQL\Driver as PostgreSQLDriver;
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Doctrine\DBAL\VersionAwarePlatformDriver as Driver;
|
||||
use Espo\Core\Utils\Database\DBAL\ConnectionFactory;
|
||||
use Espo\Core\Utils\Database\Dbal\ConnectionFactory;
|
||||
use Espo\ORM\DatabaseParams;
|
||||
use Espo\ORM\PDO\Options as PdoOptions;
|
||||
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
class PostgresqlConnectionFactory implements ConnectionFactory
|
||||
{
|
||||
@@ -50,47 +50,35 @@ class PostgresqlConnectionFactory implements ConnectionFactory
|
||||
*/
|
||||
public function create(DatabaseParams $databaseParams): Connection
|
||||
{
|
||||
$driver = $this->createDriver();
|
||||
$driver = new PostgreSQLDriver();
|
||||
|
||||
$version = $this->getDatabaseVersion() ?? '';
|
||||
$platform = $driver->createDatabasePlatformForVersion($version);
|
||||
if (!$databaseParams->getHost() || !$databaseParams->getName()) {
|
||||
throw new RuntimeException("No required database params.");
|
||||
}
|
||||
|
||||
$params = [
|
||||
'platform' => $platform,
|
||||
'pdo' => $this->pdo,
|
||||
'host' => $databaseParams->getHost(),
|
||||
'port' => $databaseParams->getPort(),
|
||||
'dbname' => $databaseParams->getName(),
|
||||
'charset' => $databaseParams->getCharset(),
|
||||
'user' => $databaseParams->getUsername(),
|
||||
'password' => $databaseParams->getPassword(),
|
||||
'driverOptions' => PdoOptions::getOptionsFromDatabaseParams($databaseParams),
|
||||
];
|
||||
|
||||
return new Connection($params, $driver);
|
||||
}
|
||||
|
||||
private function createDriver(): Driver
|
||||
{
|
||||
$driverClass = PostgreSQLDriver::class;
|
||||
|
||||
return new $driverClass();
|
||||
}
|
||||
|
||||
private function getDatabaseVersion(): ?string
|
||||
{
|
||||
$sql = "SHOW server_version";
|
||||
|
||||
$sth = $this->pdo->prepare($sql);
|
||||
$sth->execute();
|
||||
|
||||
$row = $sth->fetch(PDO::FETCH_NUM);
|
||||
|
||||
$value = $row[0] ?: null;
|
||||
|
||||
if ($value === null) {
|
||||
return null;
|
||||
if ($databaseParams->getPort() !== null) {
|
||||
$params['port'] = $databaseParams->getPort();
|
||||
}
|
||||
|
||||
return (string) $value;
|
||||
if ($databaseParams->getUsername() !== null) {
|
||||
$params['user'] = $databaseParams->getUsername();
|
||||
}
|
||||
|
||||
if ($databaseParams->getPassword() !== null) {
|
||||
$params['password'] = $databaseParams->getPassword();
|
||||
}
|
||||
|
||||
if ($databaseParams->getCharset() !== null) {
|
||||
$params['charset'] = $databaseParams->getCharset();
|
||||
}
|
||||
|
||||
return new Connection($params, $driver);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Types;
|
||||
namespace Espo\Core\Utils\Database\Dbal\Types;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\TextType;
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\DBAL\Types;
|
||||
namespace Espo\Core\Utils\Database\Dbal\Types;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types\TextType;
|
||||
@@ -33,7 +33,7 @@ use Doctrine\DBAL\Connection as DbalConnection;
|
||||
|
||||
use Espo\Core\ORM\DatabaseParamsFactory;
|
||||
use Espo\Core\ORM\PDO\PDOFactoryFactory;
|
||||
use Espo\Core\Utils\Database\DBAL\ConnectionFactoryFactory as DBALConnectionFactoryFactory;
|
||||
use Espo\Core\Utils\Database\Dbal\ConnectionFactoryFactory as DBALConnectionFactoryFactory;
|
||||
use Espo\ORM\DatabaseParams;
|
||||
|
||||
use PDO;
|
||||
|
||||
@@ -417,6 +417,10 @@ class Builder
|
||||
$result['platformOptions']['collation'] = $column->getCollation();
|
||||
}
|
||||
|
||||
if ($column->getCharset()) {
|
||||
$result['platformOptions']['charset'] = $column->getCharset();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ class Column
|
||||
private ?int $scale = null;
|
||||
private ?bool $unsigned = null;
|
||||
private ?string $collation = null;
|
||||
private ?string $charset = null;
|
||||
|
||||
private function __construct(
|
||||
private string $name,
|
||||
@@ -100,6 +101,11 @@ class Column
|
||||
return $this->collation;
|
||||
}
|
||||
|
||||
public function getCharset(): ?string
|
||||
{
|
||||
return $this->charset;
|
||||
}
|
||||
|
||||
public function withNotNull(bool $notNull = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
@@ -166,4 +172,12 @@ class Column
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withCharset(?string $charset): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->charset = $charset;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
|
||||
+22
-4
@@ -30,7 +30,8 @@
|
||||
namespace Espo\Core\Utils\Database\Schema\ColumnPreparators;
|
||||
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Espo\Core\Utils\Database\DBAL\Types\MediumtextType;
|
||||
use Espo\Core\Utils\Database\Dbal\Types\LongtextType;
|
||||
use Espo\Core\Utils\Database\Dbal\Types\MediumtextType;
|
||||
use Espo\Core\Utils\Database\Helper;
|
||||
use Espo\Core\Utils\Database\Schema\Column;
|
||||
use Espo\Core\Utils\Database\Schema\ColumnPreparator;
|
||||
@@ -152,17 +153,34 @@ class MysqlColumnPreparator implements ColumnPreparator
|
||||
->withUnsigned();
|
||||
}
|
||||
|
||||
if (
|
||||
!in_array($columnType, [
|
||||
Types::STRING,
|
||||
Types::TEXT,
|
||||
MediumtextType::NAME,
|
||||
LongtextType::NAME,
|
||||
])
|
||||
) {
|
||||
return $column;
|
||||
}
|
||||
|
||||
$collation = $binary ?
|
||||
'utf8mb4_bin' :
|
||||
'utf8mb4_unicode_ci';
|
||||
|
||||
$charset = 'utf8mb4';
|
||||
|
||||
if ($mb3) {
|
||||
$collation = $binary ?
|
||||
'utf8_bin' :
|
||||
'utf8_unicode_ci';
|
||||
'utf8mb3_bin' :
|
||||
'utf8mb3_unicode_ci';
|
||||
|
||||
$charset = 'utf8mb3';
|
||||
}
|
||||
|
||||
return $column->withCollation($collation);
|
||||
return $column
|
||||
->withCollation($collation)
|
||||
->withCharset($charset);
|
||||
}
|
||||
|
||||
private function getMaxIndexLength(): int
|
||||
|
||||
@@ -0,0 +1,311 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM - Open Source CRM application.
|
||||
* Copyright (C) 2014-2023 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* EspoCRM is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* EspoCRM is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Utils\Database\Schema;
|
||||
|
||||
use Doctrine\DBAL\Exception as DbalException;
|
||||
use Doctrine\DBAL\Schema\Column as Column;
|
||||
use Doctrine\DBAL\Schema\ColumnDiff;
|
||||
use Doctrine\DBAL\Schema\SchemaDiff;
|
||||
use Doctrine\DBAL\Schema\TableDiff;
|
||||
use Doctrine\DBAL\Types\TextType;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Espo\Core\Utils\Database\Dbal\Types\LongtextType;
|
||||
use Espo\Core\Utils\Database\Dbal\Types\MediumtextType;
|
||||
|
||||
class DiffModifier
|
||||
{
|
||||
/**
|
||||
* @throws DbalException
|
||||
*/
|
||||
public function modify(SchemaDiff $diff, bool $secondRun = false): bool
|
||||
{
|
||||
$reRun = false;
|
||||
|
||||
$diff->removedTables = [];
|
||||
|
||||
foreach ($diff->changedTables as $tableDiff) {
|
||||
$reRun = $this->amendTableDiff($tableDiff, $secondRun) || $reRun;
|
||||
}
|
||||
|
||||
return $reRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DbalException
|
||||
*/
|
||||
private function amendTableDiff(TableDiff $tableDiff, bool $secondRun): bool
|
||||
{
|
||||
$reRun = false;
|
||||
|
||||
/**
|
||||
* @todo Leave only for MariaDB?
|
||||
* MariaDB supports RENAME INDEX as of v10.5.
|
||||
* Find out how long does it take to rename fo different databases.
|
||||
*/
|
||||
// Prevent index renaming as an operation may take a lot of time.
|
||||
$tableDiff->renamedIndexes = [];
|
||||
|
||||
foreach ($tableDiff->removedColumns as $name => $column) {
|
||||
$reRun = $this->moveRemovedAutoincrementColumnToChanged($tableDiff, $column, $name) || $reRun;
|
||||
}
|
||||
|
||||
// Prevent column removal to prevent data loss.
|
||||
$tableDiff->removedColumns = [];
|
||||
|
||||
// Prevent column renaming as a not desired behavior.
|
||||
foreach ($tableDiff->renamedColumns as $renamedColumn) {
|
||||
$addedName = strtolower($renamedColumn->getName());
|
||||
$tableDiff->addedColumns[$addedName] = $renamedColumn;
|
||||
}
|
||||
|
||||
$tableDiff->renamedColumns = [];
|
||||
|
||||
foreach ($tableDiff->addedColumns as $column) {
|
||||
// Suppress autoincrement as need having a unique index first.
|
||||
$reRun = $this->amendAddedColumnAutoincrement($column) || $reRun;
|
||||
}
|
||||
|
||||
foreach ($tableDiff->changedColumns as $name => $columnDiff) {
|
||||
// Prevent decreasing length for string columns to prevent data loss.
|
||||
$this->amendColumnDiffLength($tableDiff, $columnDiff, $name);
|
||||
// Prevent longtext => mediumtext to prevent data loss.
|
||||
$this->amendColumnDiffTextType($tableDiff, $columnDiff, $name);
|
||||
// Prevent changing collation.
|
||||
$this->amendColumnDiffCollation($tableDiff, $columnDiff, $name);
|
||||
// Prevent changing charset.
|
||||
$this->amendColumnDiffCharset($tableDiff, $columnDiff, $name);
|
||||
// Prevent setting autoincrement in first run.
|
||||
if (!$secondRun) {
|
||||
$reRun = $this->amendColumnDiffAutoincrement($tableDiff, $columnDiff, $name) || $reRun;
|
||||
}
|
||||
}
|
||||
|
||||
return $reRun;
|
||||
}
|
||||
|
||||
private function amendColumnDiffLength(TableDiff $tableDiff, ColumnDiff $columnDiff, string $name): void
|
||||
{
|
||||
$fromColumn = $columnDiff->fromColumn;
|
||||
$column = $columnDiff->column;
|
||||
|
||||
if (!$fromColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array('length', $columnDiff->changedProperties)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fromLength = $fromColumn->getLength() ?? 255;
|
||||
$length = $column->getLength() ?? 255;
|
||||
|
||||
if ($fromLength <= $length) {
|
||||
return;
|
||||
}
|
||||
|
||||
$column->setLength($fromLength);
|
||||
|
||||
self::unsetChangedColumnProperty($tableDiff, $columnDiff, $name, 'length');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DbalException
|
||||
*/
|
||||
private function amendColumnDiffTextType(TableDiff $tableDiff, ColumnDiff $columnDiff, string $name): void
|
||||
{
|
||||
$fromColumn = $columnDiff->fromColumn;
|
||||
$column = $columnDiff->column;
|
||||
|
||||
if (!$fromColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array('type', $columnDiff->changedProperties)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fromType = $fromColumn->getType();
|
||||
$type = $column->getType();
|
||||
|
||||
if (
|
||||
!$fromType instanceof TextType ||
|
||||
!$type instanceof TextType
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$typePriority = [
|
||||
Types::TEXT,
|
||||
MediumtextType::NAME,
|
||||
LongtextType::NAME,
|
||||
];
|
||||
|
||||
$fromIndex = array_search($fromType->getName(), $typePriority);
|
||||
$index = array_search($type->getName(), $typePriority);
|
||||
|
||||
if ($index >= $fromIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
$column->setType(Type::getType($fromType->getName()));
|
||||
|
||||
self::unsetChangedColumnProperty($tableDiff, $columnDiff, $name, 'type');
|
||||
}
|
||||
|
||||
private function amendColumnDiffCollation(TableDiff $tableDiff, ColumnDiff $columnDiff, string $name): void
|
||||
{
|
||||
$fromColumn = $columnDiff->fromColumn;
|
||||
$column = $columnDiff->column;
|
||||
|
||||
if (!$fromColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array('collation', $columnDiff->changedProperties)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fromCollation = $fromColumn->getPlatformOption('collation');
|
||||
|
||||
if (!$fromCollation) {
|
||||
return;
|
||||
}
|
||||
|
||||
$column->setPlatformOption('collation', $fromCollation);
|
||||
|
||||
self::unsetChangedColumnProperty($tableDiff, $columnDiff, $name, 'collation');
|
||||
}
|
||||
|
||||
private function amendColumnDiffCharset(TableDiff $tableDiff, ColumnDiff $columnDiff, string $name): void
|
||||
{
|
||||
$fromColumn = $columnDiff->fromColumn;
|
||||
$column = $columnDiff->column;
|
||||
|
||||
if (!$fromColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!in_array('charset', $columnDiff->changedProperties)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fromCharset = $fromColumn->getPlatformOption('charset');
|
||||
|
||||
if (!$fromCharset) {
|
||||
return;
|
||||
}
|
||||
|
||||
$column->setPlatformOption('charset', $fromCharset);
|
||||
|
||||
self::unsetChangedColumnProperty($tableDiff, $columnDiff, $name, 'charset');
|
||||
}
|
||||
|
||||
private function amendColumnDiffAutoincrement(TableDiff $tableDiff, ColumnDiff $columnDiff, string $name): bool
|
||||
{
|
||||
$fromColumn = $columnDiff->fromColumn;
|
||||
$column = $columnDiff->column;
|
||||
|
||||
if (!$fromColumn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!in_array('autoincrement', $columnDiff->changedProperties)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$column
|
||||
->setAutoincrement(false)
|
||||
->setNotnull(false)
|
||||
->setDefault(null);
|
||||
|
||||
self::unsetChangedColumnProperty($tableDiff, $columnDiff, $name, 'autoincrement');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function amendAddedColumnAutoincrement(Column $column): bool
|
||||
{
|
||||
if (!$column->getAutoincrement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$column
|
||||
->setAutoincrement(false)
|
||||
->setNotnull(false)
|
||||
->setDefault(null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function moveRemovedAutoincrementColumnToChanged(TableDiff $tableDiff, Column $column, string $name): bool
|
||||
{
|
||||
if (!$column->getAutoincrement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$newColumn = clone $column;
|
||||
|
||||
$newColumn
|
||||
->setAutoincrement(false)
|
||||
->setNotnull(false)
|
||||
->setDefault(null);
|
||||
|
||||
$changedProperties = [
|
||||
'autoincrement',
|
||||
'notnull',
|
||||
'default',
|
||||
];
|
||||
|
||||
$tableDiff->changedColumns[$name] = new ColumnDiff($name, $newColumn, $changedProperties, $column);
|
||||
|
||||
foreach ($tableDiff->removedIndexes as $indexName => $index) {
|
||||
if ($index->getColumns() === [$name]) {
|
||||
unset($tableDiff->removedIndexes[$indexName]);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static function unsetChangedColumnProperty(
|
||||
TableDiff $tableDiff,
|
||||
ColumnDiff $columnDiff,
|
||||
string $name,
|
||||
string $property
|
||||
): void {
|
||||
|
||||
if (count($columnDiff->changedProperties) === 1) {
|
||||
unset($tableDiff->changedColumns[$name]);
|
||||
}
|
||||
|
||||
$columnDiff->changedProperties = array_diff($columnDiff->changedProperties, [$property]);
|
||||
}
|
||||
}
|
||||
@@ -32,14 +32,15 @@ namespace Espo\Core\Utils\Database\Schema;
|
||||
use Doctrine\DBAL\Connection as DbalConnection;
|
||||
use Doctrine\DBAL\Exception as DbalException;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Schema\Schema as DbalSchema;
|
||||
use Doctrine\DBAL\Schema\SchemaDiff as DbalSchemaDiff;
|
||||
use Doctrine\DBAL\Schema\AbstractSchemaManager;
|
||||
use Doctrine\DBAL\Schema\Comparator;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaDiff;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
|
||||
use Espo\Core\Binding\BindingContainerBuilder;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Database\DBAL\Schema\Comparator;
|
||||
use Espo\Core\Utils\Database\Helper;
|
||||
use Espo\Core\Utils\Log;
|
||||
use Espo\Core\Utils\Metadata\OrmMetadataData;
|
||||
@@ -51,6 +52,8 @@ use Throwable;
|
||||
*/
|
||||
class SchemaManager
|
||||
{
|
||||
/** @var AbstractSchemaManager<AbstractPlatform> */
|
||||
private AbstractSchemaManager $schemaManager;
|
||||
private Comparator $comparator;
|
||||
private Builder $builder;
|
||||
|
||||
@@ -62,8 +65,13 @@ class SchemaManager
|
||||
private Log $log,
|
||||
private Helper $helper,
|
||||
private MetadataProvider $metadataProvider,
|
||||
private DiffModifier $diffModifier,
|
||||
private InjectableFactory $injectableFactory
|
||||
) {
|
||||
$this->schemaManager = $this->getDbalConnection()->createSchemaManager();
|
||||
// Not using a platform specific comparator as it unsets a collation and charset if
|
||||
// they match a table default.
|
||||
//$this->comparator = $this->schemaManager->createComparator();
|
||||
$this->comparator = new Comparator($this->getPlatform());
|
||||
|
||||
$this->initFieldTypes();
|
||||
@@ -81,6 +89,9 @@ class SchemaManager
|
||||
return $this->helper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DbalException
|
||||
*/
|
||||
private function getPlatform(): AbstractPlatform
|
||||
{
|
||||
return $this->getDbalConnection()->getDatabasePlatform();
|
||||
@@ -88,7 +99,7 @@ class SchemaManager
|
||||
|
||||
private function getDbalConnection(): DbalConnection
|
||||
{
|
||||
return $this->getDatabaseHelper()->getDbalConnection();
|
||||
return $this->helper->getDbalConnection();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,15 +124,15 @@ class SchemaManager
|
||||
*
|
||||
* @param ?string[] $entityTypeList Specific entity types.
|
||||
* @throws SchemaException
|
||||
* @throws DbalException
|
||||
*/
|
||||
public function rebuild(?array $entityTypeList = null): bool
|
||||
{
|
||||
$currentSchema = $this->getCurrentSchema();
|
||||
|
||||
$fromSchema = $this->introspectSchema();
|
||||
$schema = $this->builder->build($this->ormMetadataData->getData(), $entityTypeList);
|
||||
|
||||
try {
|
||||
$this->processPreRebuildActions($currentSchema, $schema);
|
||||
$this->processPreRebuildActions($fromSchema, $schema);
|
||||
}
|
||||
catch (Throwable $e) {
|
||||
$this->log->alert('Rebuild database pre-rebuild error: '. $e->getMessage());
|
||||
@@ -129,14 +140,58 @@ class SchemaManager
|
||||
return false;
|
||||
}
|
||||
|
||||
$queries = $this->getDiffSql($currentSchema, $schema);
|
||||
$diff = $this->comparator->compareSchemas($fromSchema, $schema);
|
||||
$needReRun = $this->diffModifier->modify($diff);
|
||||
$sql = $this->composeDiffSql($diff);
|
||||
|
||||
$result = $this->runSql($sql);
|
||||
|
||||
if (!$result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($needReRun) {
|
||||
// Needed to handle auto-increment column creation/removal/change.
|
||||
// As an auto-increment column requires having a unique index, but
|
||||
// Doctrine DBAL does not handle this.
|
||||
$intermediateSchema = $this->introspectSchema();
|
||||
$schema = $this->builder->build($this->ormMetadataData->getData(), $entityTypeList);
|
||||
|
||||
$diff = $this->comparator->compareSchemas($intermediateSchema, $schema);
|
||||
|
||||
$this->diffModifier->modify($diff, true);
|
||||
$sql = $this->composeDiffSql($diff);
|
||||
$result = $this->runSql($sql);
|
||||
}
|
||||
|
||||
if (!$result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->processPostRebuildActions($fromSchema, $schema);
|
||||
}
|
||||
catch (Throwable $e) {
|
||||
$this->log->alert('Rebuild database post-rebuild error: ' . $e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $queries
|
||||
* @return bool
|
||||
*/
|
||||
private function runSql(array $queries): bool
|
||||
{
|
||||
$result = true;
|
||||
|
||||
$connection = $this->getDbalConnection();
|
||||
|
||||
foreach ($queries as $sql) {
|
||||
$this->log->info('SCHEMA, Execute Query: '. $sql);
|
||||
$this->log->info('Schema, query: '. $sql);
|
||||
|
||||
try {
|
||||
$connection->executeQuery($sql);
|
||||
@@ -148,51 +203,29 @@ class SchemaManager
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$this->processPostRebuildActions($currentSchema, $schema);
|
||||
}
|
||||
catch (Throwable $e) {
|
||||
$this->log->alert('Rebuild database post-rebuild error: ' . $e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current database schema.
|
||||
* Introspect and return a current database schema.
|
||||
*
|
||||
* @throws DbalException
|
||||
*/
|
||||
private function getCurrentSchema(): DbalSchema
|
||||
private function introspectSchema(): Schema
|
||||
{
|
||||
return $this->getDbalConnection()
|
||||
->getSchemaManager()
|
||||
->createSchema();
|
||||
return $this->schemaManager->introspectSchema();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SQL queries of database schema.
|
||||
*
|
||||
* @return string[] Array of SQL queries.
|
||||
* @return string[]
|
||||
* @throws DbalException
|
||||
*/
|
||||
private function toSql(DbalSchemaDiff $schema)
|
||||
private function composeDiffSql(SchemaDiff $diff): array
|
||||
{
|
||||
return $schema->toSaveSql($this->getPlatform());
|
||||
return $this->getPlatform()->getAlterSchemaSQL($diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SQL queries to get from one to another schema.
|
||||
*
|
||||
* @return string[] Array of SQL queries.
|
||||
*/
|
||||
private function getDiffSql(DbalSchema $fromSchema, DbalSchema $toSchema)
|
||||
{
|
||||
$schemaDiff = $this->comparator->compareSchemas($fromSchema, $toSchema);
|
||||
|
||||
return $this->toSql($schemaDiff);
|
||||
}
|
||||
|
||||
private function processPreRebuildActions(DbalSchema $actualSchema, DbalSchema $schema): void
|
||||
private function processPreRebuildActions(Schema $actualSchema, Schema $schema): void
|
||||
{
|
||||
$binding = BindingContainerBuilder::create()
|
||||
->bindInstance(Helper::class, $this->helper)
|
||||
@@ -205,7 +238,7 @@ class SchemaManager
|
||||
}
|
||||
}
|
||||
|
||||
private function processPostRebuildActions(DbalSchema $actualSchema, DbalSchema $schema): void
|
||||
private function processPostRebuildActions(Schema $actualSchema, Schema $schema): void
|
||||
{
|
||||
$binding = BindingContainerBuilder::create()
|
||||
->bindInstance(Helper::class, $this->helper)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"platforms": {
|
||||
"Mysql": {
|
||||
"detailsProviderClassName": "Espo\\Core\\Utils\\Database\\DetailsProviders\\MysqlDetailsProvider",
|
||||
"dbalConnectionFactoryClassName": "Espo\\Core\\Utils\\Database\\DBAL\\Factories\\MysqlConnectionFactory",
|
||||
"dbalConnectionFactoryClassName": "Espo\\Core\\Utils\\Database\\Dbal\\Factories\\MysqlConnectionFactory",
|
||||
"indexHelperClassName": "Espo\\Core\\Utils\\Database\\Orm\\IndexHelpers\\MysqlIndexHelper",
|
||||
"columnPreparatorClassName": "Espo\\Core\\Utils\\Database\\Schema\\ColumnPreparators\\MysqlColumnPreparator",
|
||||
"preRebuildActionClassNameList": [
|
||||
@@ -10,12 +10,12 @@
|
||||
],
|
||||
"postRebuildActionClassNameList": [],
|
||||
"dbalTypeClassNameMap": {
|
||||
"mediumtext": "Espo\\Core\\Utils\\Database\\DBAL\\Types\\MediumtextType",
|
||||
"longtext": "Espo\\Core\\Utils\\Database\\DBAL\\Types\\LongtextType"
|
||||
"mediumtext": "Espo\\Core\\Utils\\Database\\Dbal\\Types\\MediumtextType",
|
||||
"longtext": "Espo\\Core\\Utils\\Database\\Dbal\\Types\\LongtextType"
|
||||
}
|
||||
},
|
||||
"Postgresql": {
|
||||
"dbalConnectionFactoryClassName": "Espo\\Core\\Utils\\Database\\DBAL\\Factories\\PostgresqlConnectionFactory"
|
||||
"dbalConnectionFactoryClassName": "Espo\\Core\\Utils\\Database\\Dbal\\Factories\\PostgresqlConnectionFactory"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@
|
||||
"nesbot/carbon": "^2.26",
|
||||
"zbateson/mail-mime-parser": "1.3.*",
|
||||
"phpoffice/phpspreadsheet": "^1.16",
|
||||
"doctrine/dbal": "^3.3.3",
|
||||
"doctrine/dbal": "^3.5.3",
|
||||
"league/flysystem-async-aws-s3": "^2.0",
|
||||
"johngrogg/ics-parser": "^3.0",
|
||||
"phpseclib/phpseclib": "^3.0",
|
||||
|
||||
Generated
+20
-20
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "ffda69f674ecffbd773206c0e88aeb52",
|
||||
"content-hash": "be0031b1415143f100d0d315151d8bc5",
|
||||
"packages": [
|
||||
{
|
||||
"name": "async-aws/core",
|
||||
@@ -374,38 +374,38 @@
|
||||
},
|
||||
{
|
||||
"name": "doctrine/dbal",
|
||||
"version": "3.3.3",
|
||||
"version": "3.5.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/dbal.git",
|
||||
"reference": "82331b861727c15b1f457ef05a8729e508e7ead5"
|
||||
"reference": "88fa7e5189fd5ec6682477044264dc0ed4e3aa1e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/82331b861727c15b1f457ef05a8729e508e7ead5",
|
||||
"reference": "82331b861727c15b1f457ef05a8729e508e7ead5",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/88fa7e5189fd5ec6682477044264dc0ed4e3aa1e",
|
||||
"reference": "88fa7e5189fd5ec6682477044264dc0ed4e3aa1e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer-runtime-api": "^2",
|
||||
"doctrine/cache": "^1.11|^2.0",
|
||||
"doctrine/deprecations": "^0.5.3",
|
||||
"doctrine/event-manager": "^1.0",
|
||||
"php": "^7.3 || ^8.0",
|
||||
"doctrine/deprecations": "^0.5.3|^1",
|
||||
"doctrine/event-manager": "^1|^2",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"psr/cache": "^1|^2|^3",
|
||||
"psr/log": "^1|^2|^3"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "9.0.0",
|
||||
"jetbrains/phpstorm-stubs": "2021.1",
|
||||
"phpstan/phpstan": "1.4.6",
|
||||
"phpstan/phpstan-strict-rules": "^1.1",
|
||||
"phpunit/phpunit": "9.5.16",
|
||||
"psalm/plugin-phpunit": "0.16.1",
|
||||
"squizlabs/php_codesniffer": "3.6.2",
|
||||
"symfony/cache": "^5.2|^6.0",
|
||||
"symfony/console": "^2.7|^3.0|^4.0|^5.0|^6.0",
|
||||
"vimeo/psalm": "4.22.0"
|
||||
"doctrine/coding-standard": "11.0.0",
|
||||
"jetbrains/phpstorm-stubs": "2022.3",
|
||||
"phpstan/phpstan": "1.9.4",
|
||||
"phpstan/phpstan-strict-rules": "^1.4",
|
||||
"phpunit/phpunit": "9.5.27",
|
||||
"psalm/plugin-phpunit": "0.18.4",
|
||||
"squizlabs/php_codesniffer": "3.7.1",
|
||||
"symfony/cache": "^5.4|^6.0",
|
||||
"symfony/console": "^4.4|^5.4|^6.0",
|
||||
"vimeo/psalm": "4.30.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/console": "For helpful console commands such as SQL execution and import of files."
|
||||
@@ -465,7 +465,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/dbal/issues",
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.3.3"
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.5.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -481,7 +481,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-03-09T15:39:50+00:00"
|
||||
"time": "2023-01-12T10:21:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/deprecations",
|
||||
|
||||
@@ -139,6 +139,8 @@ class Tester
|
||||
|
||||
protected function getTestConfigData()
|
||||
{
|
||||
$this->changeDirToBase();
|
||||
|
||||
if (file_exists($this->configPath)) {
|
||||
$data = include($this->configPath);
|
||||
}
|
||||
@@ -258,12 +260,19 @@ class Tester
|
||||
$this->loadData();
|
||||
}
|
||||
|
||||
public function terminate()
|
||||
private function changeDirToBase(): void
|
||||
{
|
||||
$baseDir = str_replace('/' . $this->installPath, '', getcwd());
|
||||
$installPath = str_replace('/', DIRECTORY_SEPARATOR, $this->installPath);
|
||||
|
||||
$baseDir = str_replace(DIRECTORY_SEPARATOR . $installPath, '', getcwd());
|
||||
|
||||
chdir($baseDir);
|
||||
set_include_path($baseDir);
|
||||
}
|
||||
|
||||
public function terminate()
|
||||
{
|
||||
$this->changeDirToBase();
|
||||
|
||||
if ($this->getParam('fullReset')) {
|
||||
$this->saveTestConfigData('lastModifiedTime', null);
|
||||
|
||||
@@ -43,7 +43,7 @@ class AutoIncrementFieldTest extends Base
|
||||
$this->assertEquals('UNI', $column['COLUMN_KEY']);
|
||||
}
|
||||
|
||||
public function testColumnOnExistingTable()
|
||||
public function testColumnOnExistingTable(): void
|
||||
{
|
||||
$this->updateDefs('Test', 'testAutoIncrement', [
|
||||
'type' => 'autoincrement',
|
||||
@@ -59,19 +59,17 @@ class AutoIncrementFieldTest extends Base
|
||||
$this->assertEquals('UNI', $column['COLUMN_KEY']);
|
||||
}
|
||||
|
||||
public function testDeleteColumnOnExistingTable()
|
||||
public function testDeleteColumnOnExistingTable(): void
|
||||
{
|
||||
// 1. Create "testAutoIncrement" field
|
||||
$this->testColumnOnExistingTable();
|
||||
|
||||
// 2. Delete "testAutoIncrement" field
|
||||
$metadata = $this->getContainer()->get('metadata');
|
||||
$entityDefs = $metadata->delete('entityDefs', 'Test', [
|
||||
'fields.testAutoIncrement',
|
||||
]);
|
||||
$metadata->save();
|
||||
$this->getMetadata()->delete('entityDefs', 'Test', ['fields.testAutoIncrement']);
|
||||
$this->getMetadata()->save();
|
||||
|
||||
$this->getContainer()->get('dataManager')->rebuild([$entityName]);
|
||||
// Issue that it requires rebuilding between removing and adding a new indexes.
|
||||
$this->getDataManager()->rebuild();
|
||||
|
||||
$column = $this->getColumnInfo('Test', 'testAutoIncrement');
|
||||
|
||||
@@ -83,18 +81,18 @@ class AutoIncrementFieldTest extends Base
|
||||
$this->assertEmpty($column['COLUMN_KEY']);
|
||||
}
|
||||
|
||||
public function testDeleteCreateColumnOnExistingTable()
|
||||
public function testDeleteCreateColumnOnExistingTable(): void
|
||||
{
|
||||
// 1. Create "testAutoIncrement" field
|
||||
$this->testColumnOnExistingTable();
|
||||
|
||||
// 2. Delete "testAutoIncrement" field
|
||||
$metadata = $this->getContainer()->get('metadata');
|
||||
$entityDefs = $metadata->delete('entityDefs', 'Test', [
|
||||
'fields.testAutoIncrement',
|
||||
]);
|
||||
$metadata = $this->getMetadata();
|
||||
$metadata->delete('entityDefs', 'Test', ['fields.testAutoIncrement']);
|
||||
$metadata->save();
|
||||
|
||||
$this->getDataManager()->rebuild();
|
||||
|
||||
// 3. Create "testAutoIncrement2" field
|
||||
$this->updateDefs('Test', 'testAutoIncrement2', [
|
||||
'type' => 'autoincrement',
|
||||
|
||||
@@ -91,7 +91,7 @@ abstract class Base extends \tests\integration\Core\BaseTestCase
|
||||
$metadata->set('entityDefs', 'Test', $entityDefs);
|
||||
$metadata->save();
|
||||
|
||||
$this->getContainer()->get('dataManager')->rebuild([$entityName]);
|
||||
$this->getDataManager()->rebuild([$entityName]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,24 +29,18 @@
|
||||
|
||||
namespace tests\integration\Espo\Core\Utils\Database;
|
||||
|
||||
use tests\unit\ReflectionHelper;
|
||||
use Espo\Core\Utils\Database\Helper;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use PDO;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class HelperTest extends \tests\integration\Core\BaseTestCase
|
||||
{
|
||||
/** @var ?Helper */
|
||||
protected $helper;
|
||||
protected $reflection;
|
||||
|
||||
protected function initTest()
|
||||
{
|
||||
$this->helper = $this->getInjectableFactory()->create(Helper::class);
|
||||
|
||||
$this->reflection = new ReflectionHelper($this->helper);
|
||||
$this->helper = $this->getInjectableFactory()->create(Helper::class);
|
||||
}
|
||||
|
||||
private function getDatabaseInfo()
|
||||
@@ -84,41 +78,7 @@ class HelperTest extends \tests\integration\Core\BaseTestCase
|
||||
{
|
||||
$this->initTest();
|
||||
|
||||
$this->assertInstanceOf(PDO::class, $this->helper->getPdoConnection());
|
||||
}
|
||||
|
||||
public function testGetMaxIndexLength()
|
||||
{
|
||||
$this->initTest();
|
||||
|
||||
$databaseInfo = $this->getDatabaseInfo();
|
||||
if (empty($databaseInfo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$expectedMaxIndexLength = 767;
|
||||
|
||||
switch ($databaseInfo['type']) {
|
||||
case 'mysql':
|
||||
if (version_compare($databaseInfo['version'], '5.7.0') >= 0) {
|
||||
$expectedMaxIndexLength = 3072;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'mariadb':
|
||||
if (version_compare($databaseInfo['version'], '10.2.2') >= 0) {
|
||||
$expectedMaxIndexLength = 3072;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$engine = $this->reflection->invokeMethod('getTableEngine');
|
||||
$result = ($engine == 'MyISAM') ? 1000 : $expectedMaxIndexLength;
|
||||
$this->assertEquals($result, $this->helper->getMaxIndexLength());
|
||||
|
||||
$engine = $this->reflection->invokeMethod('getTableEngine', ['account']);
|
||||
$result = ($engine == 'MyISAM') ? 1000 : $expectedMaxIndexLength;
|
||||
$this->assertEquals($expectedMaxIndexLength, $this->helper->getMaxIndexLength('account'));
|
||||
$this->assertInstanceOf(PDO::class, $this->helper->getPDO());
|
||||
}
|
||||
|
||||
public function testGetDatabaseInfo()
|
||||
@@ -130,8 +90,8 @@ class HelperTest extends \tests\integration\Core\BaseTestCase
|
||||
return;
|
||||
}
|
||||
|
||||
$this->assertEquals($databaseInfo['type'], strtolower($this->helper->getDatabaseType()));
|
||||
$this->assertEquals($databaseInfo['version'], $this->helper->getDatabaseVersion());
|
||||
$this->assertEquals($databaseInfo['type'], strtolower($this->helper->getType()));
|
||||
$this->assertEquals($databaseInfo['version'], $this->helper->getVersion());
|
||||
}
|
||||
|
||||
public function testGetDatabaseType()
|
||||
@@ -145,11 +105,11 @@ class HelperTest extends \tests\integration\Core\BaseTestCase
|
||||
|
||||
switch ($databaseInfo['type']) {
|
||||
case 'mysql':
|
||||
$this->assertEquals('MySQL', $this->helper->getDatabaseType());
|
||||
$this->assertEquals('MySQL', $this->helper->getType());
|
||||
break;
|
||||
|
||||
case 'mariadb':
|
||||
$this->assertEquals('MariaDB', $this->helper->getDatabaseType());
|
||||
$this->assertEquals('MariaDB', $this->helper->getType());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +68,8 @@ class TextFieldTest extends Base
|
||||
]);
|
||||
$this->getContainer()->get('metadata')->save();
|
||||
|
||||
$this->getDataManager()->rebuildDatabase();
|
||||
|
||||
$column = $this->getColumnInfo('Test', 'testText');
|
||||
|
||||
$this->assertNotEmpty($column);
|
||||
|
||||
@@ -210,4 +210,18 @@ class VarcharFieldTest extends Base
|
||||
$this->assertEquals('test-default', $column['COLUMN_DEFAULT']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure columns not removed.
|
||||
*/
|
||||
public function testRemoveField(): void
|
||||
{
|
||||
$this->getMetadata()->delete('entityDefs', 'Test', ['fields.testVarchar']);
|
||||
$this->getMetadata()->save();
|
||||
$this->getDataManager()->rebuildDatabase();
|
||||
|
||||
$column = $this->getColumnInfo('Test', 'testVarchar');
|
||||
|
||||
$this->assertTrue((bool) $column);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user