welcome back to dyb-tech
This commit is contained in:
+223
@@ -0,0 +1,223 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_map;
|
||||
use function crc32;
|
||||
use function dechex;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function str_replace;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function strtoupper;
|
||||
use function substr;
|
||||
|
||||
/**
|
||||
* The abstract asset allows to reset the name of all assets without publishing this to the public userland.
|
||||
*
|
||||
* This encapsulation hack is necessary to keep a consistent state of the database schema. Say we have a list of tables
|
||||
* array($tableName => Table($tableName)); if you want to rename the table, you have to make sure
|
||||
*/
|
||||
abstract class AbstractAsset
|
||||
{
|
||||
/** @var string */
|
||||
protected $_name = '';
|
||||
|
||||
/**
|
||||
* Namespace of the asset. If none isset the default namespace is assumed.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $_namespace;
|
||||
|
||||
/** @var bool */
|
||||
protected $_quoted = false;
|
||||
|
||||
/**
|
||||
* Sets the name of this asset.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _setName($name)
|
||||
{
|
||||
if ($this->isIdentifierQuoted($name)) {
|
||||
$this->_quoted = true;
|
||||
$name = $this->trimQuotes($name);
|
||||
}
|
||||
|
||||
if (strpos($name, '.') !== false) {
|
||||
$parts = explode('.', $name);
|
||||
$this->_namespace = $parts[0];
|
||||
$name = $parts[1];
|
||||
}
|
||||
|
||||
$this->_name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this asset in the default namespace?
|
||||
*
|
||||
* @param string $defaultNamespaceName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isInDefaultNamespace($defaultNamespaceName)
|
||||
{
|
||||
return $this->_namespace === $defaultNamespaceName || $this->_namespace === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the namespace name of this asset.
|
||||
*
|
||||
* If NULL is returned this means the default namespace is used.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getNamespaceName()
|
||||
{
|
||||
return $this->_namespace;
|
||||
}
|
||||
|
||||
/**
|
||||
* The shortest name is stripped of the default namespace. All other
|
||||
* namespaced elements are returned as full-qualified names.
|
||||
*
|
||||
* @param string|null $defaultNamespaceName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getShortestName($defaultNamespaceName)
|
||||
{
|
||||
$shortestName = $this->getName();
|
||||
if ($this->_namespace === $defaultNamespaceName) {
|
||||
$shortestName = $this->_name;
|
||||
}
|
||||
|
||||
return strtolower($shortestName);
|
||||
}
|
||||
|
||||
/**
|
||||
* The normalized name is full-qualified and lower-cased. Lower-casing is
|
||||
* actually wrong, but we have to do it to keep our sanity. If you are
|
||||
* using database objects that only differentiate in the casing (FOO vs
|
||||
* Foo) then you will NOT be able to use Doctrine Schema abstraction.
|
||||
*
|
||||
* Every non-namespaced element is prefixed with the default namespace
|
||||
* name which is passed as argument to this method.
|
||||
*
|
||||
* @deprecated Use {@see getNamespaceName()} and {@see getName()} instead.
|
||||
*
|
||||
* @param string $defaultNamespaceName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getFullQualifiedName($defaultNamespaceName)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4814',
|
||||
'AbstractAsset::getFullQualifiedName() is deprecated.'
|
||||
. ' Use AbstractAsset::getNamespaceName() and ::getName() instead.',
|
||||
);
|
||||
|
||||
$name = $this->getName();
|
||||
if ($this->_namespace === null) {
|
||||
$name = $defaultNamespaceName . '.' . $name;
|
||||
}
|
||||
|
||||
return strtolower($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this asset's name is quoted.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isQuoted()
|
||||
{
|
||||
return $this->_quoted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this identifier is quoted.
|
||||
*
|
||||
* @param string $identifier
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isIdentifierQuoted($identifier)
|
||||
{
|
||||
return isset($identifier[0]) && ($identifier[0] === '`' || $identifier[0] === '"' || $identifier[0] === '[');
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim quotes from the identifier.
|
||||
*
|
||||
* @param string $identifier
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function trimQuotes($identifier)
|
||||
{
|
||||
return str_replace(['`', '"', '[', ']'], '', $identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this schema asset.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
if ($this->_namespace !== null) {
|
||||
return $this->_namespace . '.' . $this->_name;
|
||||
}
|
||||
|
||||
return $this->_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the quoted representation of this asset but only if it was defined with one. Otherwise
|
||||
* return the plain unquoted value as inserted.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedName(AbstractPlatform $platform)
|
||||
{
|
||||
$keywords = $platform->getReservedKeywordsList();
|
||||
$parts = explode('.', $this->getName());
|
||||
foreach ($parts as $k => $v) {
|
||||
$parts[$k] = $this->_quoted || $keywords->isKeyword($v) ? $platform->quoteIdentifier($v) : $v;
|
||||
}
|
||||
|
||||
return implode('.', $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an identifier from a list of column names obeying a certain string length.
|
||||
*
|
||||
* This is especially important for Oracle, since it does not allow identifiers larger than 30 chars,
|
||||
* however building idents automatically for foreign keys, composite keys or such can easily create
|
||||
* very long names.
|
||||
*
|
||||
* @param string[] $columnNames
|
||||
* @param string $prefix
|
||||
* @param int $maxSize
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _generateIdentifierName($columnNames, $prefix = '', $maxSize = 30)
|
||||
{
|
||||
$hash = implode('', array_map(static function ($column): string {
|
||||
return dechex(crc32($column));
|
||||
}, $columnNames));
|
||||
|
||||
return strtoupper(substr($prefix . '_' . $hash, 0, $maxSize));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+466
@@ -0,0 +1,466 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Schema\Exception\UnknownColumnOption;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_merge;
|
||||
use function is_numeric;
|
||||
use function method_exists;
|
||||
|
||||
/**
|
||||
* Object representation of a database column.
|
||||
*/
|
||||
class Column extends AbstractAsset
|
||||
{
|
||||
/** @var Type */
|
||||
protected $_type;
|
||||
|
||||
/** @var int|null */
|
||||
protected $_length;
|
||||
|
||||
/** @var int */
|
||||
protected $_precision = 10;
|
||||
|
||||
/** @var int */
|
||||
protected $_scale = 0;
|
||||
|
||||
/** @var bool */
|
||||
protected $_unsigned = false;
|
||||
|
||||
/** @var bool */
|
||||
protected $_fixed = false;
|
||||
|
||||
/** @var bool */
|
||||
protected $_notnull = true;
|
||||
|
||||
/** @var mixed */
|
||||
protected $_default;
|
||||
|
||||
/** @var bool */
|
||||
protected $_autoincrement = false;
|
||||
|
||||
/** @var mixed[] */
|
||||
protected $_platformOptions = [];
|
||||
|
||||
/** @var string|null */
|
||||
protected $_columnDefinition;
|
||||
|
||||
/** @var string|null */
|
||||
protected $_comment;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link $_platformOptions} instead
|
||||
*
|
||||
* @var mixed[]
|
||||
*/
|
||||
protected $_customSchemaOptions = [];
|
||||
|
||||
/**
|
||||
* Creates a new Column.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed[] $options
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function __construct($name, Type $type, array $options = [])
|
||||
{
|
||||
$this->_setName($name);
|
||||
$this->setType($type);
|
||||
$this->setOptions($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $options
|
||||
*
|
||||
* @return Column
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function setOptions(array $options)
|
||||
{
|
||||
foreach ($options as $name => $value) {
|
||||
$method = 'set' . $name;
|
||||
|
||||
if (! method_exists($this, $method)) {
|
||||
throw UnknownColumnOption::new($name);
|
||||
}
|
||||
|
||||
$this->$method($value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return Column */
|
||||
public function setType(Type $type)
|
||||
{
|
||||
$this->_type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|null $length
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public function setLength($length)
|
||||
{
|
||||
if ($length !== null) {
|
||||
$this->_length = (int) $length;
|
||||
} else {
|
||||
$this->_length = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $precision
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public function setPrecision($precision)
|
||||
{
|
||||
if (! is_numeric($precision)) {
|
||||
$precision = 10; // defaults to 10 when no valid precision is given.
|
||||
}
|
||||
|
||||
$this->_precision = (int) $precision;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $scale
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public function setScale($scale)
|
||||
{
|
||||
if (! is_numeric($scale)) {
|
||||
$scale = 0;
|
||||
}
|
||||
|
||||
$this->_scale = (int) $scale;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $unsigned
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public function setUnsigned($unsigned)
|
||||
{
|
||||
$this->_unsigned = (bool) $unsigned;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $fixed
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public function setFixed($fixed)
|
||||
{
|
||||
$this->_fixed = (bool) $fixed;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $notnull
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public function setNotnull($notnull)
|
||||
{
|
||||
$this->_notnull = (bool) $notnull;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public function setDefault($default)
|
||||
{
|
||||
$this->_default = $default;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $platformOptions
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public function setPlatformOptions(array $platformOptions)
|
||||
{
|
||||
$this->_platformOptions = $platformOptions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public function setPlatformOption($name, $value)
|
||||
{
|
||||
$this->_platformOptions[$name] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $value
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public function setColumnDefinition($value)
|
||||
{
|
||||
$this->_columnDefinition = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return Type */
|
||||
public function getType()
|
||||
{
|
||||
return $this->_type;
|
||||
}
|
||||
|
||||
/** @return int|null */
|
||||
public function getLength()
|
||||
{
|
||||
return $this->_length;
|
||||
}
|
||||
|
||||
/** @return int */
|
||||
public function getPrecision()
|
||||
{
|
||||
return $this->_precision;
|
||||
}
|
||||
|
||||
/** @return int */
|
||||
public function getScale()
|
||||
{
|
||||
return $this->_scale;
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function getUnsigned()
|
||||
{
|
||||
return $this->_unsigned;
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function getFixed()
|
||||
{
|
||||
return $this->_fixed;
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function getNotnull()
|
||||
{
|
||||
return $this->_notnull;
|
||||
}
|
||||
|
||||
/** @return mixed */
|
||||
public function getDefault()
|
||||
{
|
||||
return $this->_default;
|
||||
}
|
||||
|
||||
/** @return mixed[] */
|
||||
public function getPlatformOptions()
|
||||
{
|
||||
return $this->_platformOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPlatformOption($name)
|
||||
{
|
||||
return isset($this->_platformOptions[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getPlatformOption($name)
|
||||
{
|
||||
return $this->_platformOptions[$name];
|
||||
}
|
||||
|
||||
/** @return string|null */
|
||||
public function getColumnDefinition()
|
||||
{
|
||||
return $this->_columnDefinition;
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function getAutoincrement()
|
||||
{
|
||||
return $this->_autoincrement;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $flag
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public function setAutoincrement($flag)
|
||||
{
|
||||
$this->_autoincrement = $flag;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $comment
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public function setComment($comment)
|
||||
{
|
||||
$this->_comment = $comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return string|null */
|
||||
public function getComment()
|
||||
{
|
||||
return $this->_comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link setPlatformOption()} instead
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public function setCustomSchemaOption($name, $value)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5476',
|
||||
'Column::setCustomSchemaOption() is deprecated. Use setPlatformOption() instead.',
|
||||
);
|
||||
|
||||
$this->_customSchemaOptions[$name] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link hasPlatformOption()} instead
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasCustomSchemaOption($name)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5476',
|
||||
'Column::hasCustomSchemaOption() is deprecated. Use hasPlatformOption() instead.',
|
||||
);
|
||||
|
||||
return isset($this->_customSchemaOptions[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link getPlatformOption()} instead
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCustomSchemaOption($name)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5476',
|
||||
'Column::getCustomSchemaOption() is deprecated. Use getPlatformOption() instead.',
|
||||
);
|
||||
|
||||
return $this->_customSchemaOptions[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link setPlatformOptions()} instead
|
||||
*
|
||||
* @param mixed[] $customSchemaOptions
|
||||
*
|
||||
* @return Column
|
||||
*/
|
||||
public function setCustomSchemaOptions(array $customSchemaOptions)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5476',
|
||||
'Column::setCustomSchemaOptions() is deprecated. Use setPlatformOptions() instead.',
|
||||
);
|
||||
|
||||
$this->_customSchemaOptions = $customSchemaOptions;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link getPlatformOptions()} instead
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getCustomSchemaOptions()
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5476',
|
||||
'Column::getCustomSchemaOptions() is deprecated. Use getPlatformOptions() instead.',
|
||||
);
|
||||
|
||||
return $this->_customSchemaOptions;
|
||||
}
|
||||
|
||||
/** @return mixed[] */
|
||||
public function toArray()
|
||||
{
|
||||
return array_merge([
|
||||
'name' => $this->_name,
|
||||
'type' => $this->_type,
|
||||
'default' => $this->_default,
|
||||
'notnull' => $this->_notnull,
|
||||
'length' => $this->_length,
|
||||
'precision' => $this->_precision,
|
||||
'scale' => $this->_scale,
|
||||
'fixed' => $this->_fixed,
|
||||
'unsigned' => $this->_unsigned,
|
||||
'autoincrement' => $this->_autoincrement,
|
||||
'columnDefinition' => $this->_columnDefinition,
|
||||
'comment' => $this->_comment,
|
||||
], $this->_platformOptions, $this->_customSchemaOptions);
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function in_array;
|
||||
|
||||
/**
|
||||
* Represents the change of a column.
|
||||
*/
|
||||
class ColumnDiff
|
||||
{
|
||||
/**
|
||||
* @deprecated Use {@see $fromColumn} and {@see Column::getName()} instead.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $oldColumnName;
|
||||
|
||||
/**
|
||||
* @internal Use {@see getNewColumn()} instead.
|
||||
*
|
||||
* @var Column
|
||||
*/
|
||||
public $column;
|
||||
|
||||
/**
|
||||
* @deprecated Use {@see hasTypeChanged()}, {@see hasLengthChanged()}, {@see hasPrecisionChanged()},
|
||||
* {@see hasScaleChanged()}, {@see hasUnsignedChanged()}, {@see hasFixedChanged()}, {@see hasNotNullChanged()},
|
||||
* {@see hasDefaultChanged()}, {@see hasAutoIncrementChanged()} or {@see hasCommentChanged()} instead.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $changedProperties = [];
|
||||
|
||||
/**
|
||||
* @internal Use {@see getOldColumn()} instead.
|
||||
*
|
||||
* @var Column|null
|
||||
*/
|
||||
public $fromColumn;
|
||||
|
||||
/**
|
||||
* @internal The diff can be only instantiated by a {@see Comparator}.
|
||||
*
|
||||
* @param string $oldColumnName
|
||||
* @param string[] $changedProperties
|
||||
*/
|
||||
public function __construct(
|
||||
$oldColumnName,
|
||||
Column $column,
|
||||
array $changedProperties = [],
|
||||
?Column $fromColumn = null
|
||||
) {
|
||||
if ($fromColumn === null) {
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4785',
|
||||
'Not passing the $fromColumn to %s is deprecated.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
$this->oldColumnName = $oldColumnName;
|
||||
$this->column = $column;
|
||||
$this->changedProperties = $changedProperties;
|
||||
$this->fromColumn = $fromColumn;
|
||||
}
|
||||
|
||||
public function getOldColumn(): ?Column
|
||||
{
|
||||
return $this->fromColumn;
|
||||
}
|
||||
|
||||
public function getNewColumn(): Column
|
||||
{
|
||||
return $this->column;
|
||||
}
|
||||
|
||||
public function hasTypeChanged(): bool
|
||||
{
|
||||
return $this->hasChanged('type');
|
||||
}
|
||||
|
||||
public function hasLengthChanged(): bool
|
||||
{
|
||||
return $this->hasChanged('length');
|
||||
}
|
||||
|
||||
public function hasPrecisionChanged(): bool
|
||||
{
|
||||
return $this->hasChanged('precision');
|
||||
}
|
||||
|
||||
public function hasScaleChanged(): bool
|
||||
{
|
||||
return $this->hasChanged('scale');
|
||||
}
|
||||
|
||||
public function hasUnsignedChanged(): bool
|
||||
{
|
||||
return $this->hasChanged('unsigned');
|
||||
}
|
||||
|
||||
public function hasFixedChanged(): bool
|
||||
{
|
||||
return $this->hasChanged('fixed');
|
||||
}
|
||||
|
||||
public function hasNotNullChanged(): bool
|
||||
{
|
||||
return $this->hasChanged('notnull');
|
||||
}
|
||||
|
||||
public function hasDefaultChanged(): bool
|
||||
{
|
||||
return $this->hasChanged('default');
|
||||
}
|
||||
|
||||
public function hasAutoIncrementChanged(): bool
|
||||
{
|
||||
return $this->hasChanged('autoincrement');
|
||||
}
|
||||
|
||||
public function hasCommentChanged(): bool
|
||||
{
|
||||
return $this->hasChanged('comment');
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@see hasTypeChanged()}, {@see hasLengthChanged()}, {@see hasPrecisionChanged()},
|
||||
* {@see hasScaleChanged()}, {@see hasUnsignedChanged()}, {@see hasFixedChanged()}, {@see hasNotNullChanged()},
|
||||
* {@see hasDefaultChanged()}, {@see hasAutoIncrementChanged()} or {@see hasCommentChanged()} instead.
|
||||
*
|
||||
* @param string $propertyName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasChanged($propertyName)
|
||||
{
|
||||
return in_array($propertyName, $this->changedProperties, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@see $fromColumn} instead.
|
||||
*
|
||||
* @return Identifier
|
||||
*/
|
||||
public function getOldColumnName()
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5622',
|
||||
'%s is deprecated. Use $fromColumn instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
if ($this->fromColumn !== null) {
|
||||
$name = $this->fromColumn->getName();
|
||||
$quote = $this->fromColumn->isQuoted();
|
||||
} else {
|
||||
$name = $this->oldColumnName;
|
||||
$quote = false;
|
||||
}
|
||||
|
||||
return new Identifier($name, $quote);
|
||||
}
|
||||
}
|
||||
+716
@@ -0,0 +1,716 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Types;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_intersect_key;
|
||||
use function array_key_exists;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_unique;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function get_class;
|
||||
use function sprintf;
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
* Compares two Schemas and return an instance of SchemaDiff.
|
||||
*
|
||||
* @method SchemaDiff compareSchemas(Schema $fromSchema, Schema $toSchema)
|
||||
*/
|
||||
class Comparator
|
||||
{
|
||||
private ?AbstractPlatform $platform;
|
||||
|
||||
/** @internal The comparator can be only instantiated by a schema manager. */
|
||||
public function __construct(?AbstractPlatform $platform = null)
|
||||
{
|
||||
if ($platform === null) {
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4746',
|
||||
'Not passing a $platform to %s is deprecated.'
|
||||
. ' Use AbstractSchemaManager::createComparator() to instantiate the comparator.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
$this->platform = $platform;
|
||||
}
|
||||
|
||||
/** @param list<mixed> $args */
|
||||
public function __call(string $method, array $args): SchemaDiff
|
||||
{
|
||||
if ($method !== 'compareSchemas') {
|
||||
throw new BadMethodCallException(sprintf('Unknown method "%s"', $method));
|
||||
}
|
||||
|
||||
return $this->doCompareSchemas(...$args);
|
||||
}
|
||||
|
||||
/** @param list<mixed> $args */
|
||||
public static function __callStatic(string $method, array $args): SchemaDiff
|
||||
{
|
||||
if ($method !== 'compareSchemas') {
|
||||
throw new BadMethodCallException(sprintf('Unknown method "%s"', $method));
|
||||
}
|
||||
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4707',
|
||||
'Calling %s::%s() statically is deprecated.',
|
||||
self::class,
|
||||
$method,
|
||||
);
|
||||
|
||||
$comparator = new self();
|
||||
|
||||
return $comparator->doCompareSchemas(...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a SchemaDiff object containing the differences between the schemas $fromSchema and $toSchema.
|
||||
*
|
||||
* This method should be called non-statically since it will be declared as non-static in the next major release.
|
||||
*
|
||||
* @return SchemaDiff
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
private function doCompareSchemas(
|
||||
Schema $fromSchema,
|
||||
Schema $toSchema
|
||||
) {
|
||||
$createdSchemas = [];
|
||||
$droppedSchemas = [];
|
||||
$createdTables = [];
|
||||
$alteredTables = [];
|
||||
$droppedTables = [];
|
||||
$createdSequences = [];
|
||||
$alteredSequences = [];
|
||||
$droppedSequences = [];
|
||||
|
||||
$orphanedForeignKeys = [];
|
||||
|
||||
$foreignKeysToTable = [];
|
||||
|
||||
foreach ($toSchema->getNamespaces() as $namespace) {
|
||||
if ($fromSchema->hasNamespace($namespace)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$createdSchemas[$namespace] = $namespace;
|
||||
}
|
||||
|
||||
foreach ($fromSchema->getNamespaces() as $namespace) {
|
||||
if ($toSchema->hasNamespace($namespace)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$droppedSchemas[$namespace] = $namespace;
|
||||
}
|
||||
|
||||
foreach ($toSchema->getTables() as $table) {
|
||||
$tableName = $table->getShortestName($toSchema->getName());
|
||||
if (! $fromSchema->hasTable($tableName)) {
|
||||
$createdTables[$tableName] = $toSchema->getTable($tableName);
|
||||
} else {
|
||||
$tableDifferences = $this->diffTable(
|
||||
$fromSchema->getTable($tableName),
|
||||
$toSchema->getTable($tableName),
|
||||
);
|
||||
|
||||
if ($tableDifferences !== false) {
|
||||
$alteredTables[$tableName] = $tableDifferences;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Check if there are tables removed */
|
||||
foreach ($fromSchema->getTables() as $table) {
|
||||
$tableName = $table->getShortestName($fromSchema->getName());
|
||||
|
||||
$table = $fromSchema->getTable($tableName);
|
||||
if (! $toSchema->hasTable($tableName)) {
|
||||
$droppedTables[$tableName] = $table;
|
||||
}
|
||||
|
||||
// also remember all foreign keys that point to a specific table
|
||||
foreach ($table->getForeignKeys() as $foreignKey) {
|
||||
$foreignTable = strtolower($foreignKey->getForeignTableName());
|
||||
if (! isset($foreignKeysToTable[$foreignTable])) {
|
||||
$foreignKeysToTable[$foreignTable] = [];
|
||||
}
|
||||
|
||||
$foreignKeysToTable[$foreignTable][] = $foreignKey;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($droppedTables as $tableName => $table) {
|
||||
if (! isset($foreignKeysToTable[$tableName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($foreignKeysToTable[$tableName] as $foreignKey) {
|
||||
if (isset($droppedTables[strtolower($foreignKey->getLocalTableName())])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$orphanedForeignKeys[] = $foreignKey;
|
||||
}
|
||||
|
||||
// deleting duplicated foreign keys present on both on the orphanedForeignKey
|
||||
// and the removedForeignKeys from changedTables
|
||||
foreach ($foreignKeysToTable[$tableName] as $foreignKey) {
|
||||
// strtolower the table name to make if compatible with getShortestName
|
||||
$localTableName = strtolower($foreignKey->getLocalTableName());
|
||||
if (! isset($alteredTables[$localTableName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($alteredTables[$localTableName]->getDroppedForeignKeys() as $droppedForeignKey) {
|
||||
assert($droppedForeignKey instanceof ForeignKeyConstraint);
|
||||
|
||||
// We check if the key is from the removed table if not we skip.
|
||||
if ($tableName !== strtolower($droppedForeignKey->getForeignTableName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$alteredTables[$localTableName]->unsetDroppedForeignKey($droppedForeignKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($toSchema->getSequences() as $sequence) {
|
||||
$sequenceName = $sequence->getShortestName($toSchema->getName());
|
||||
if (! $fromSchema->hasSequence($sequenceName)) {
|
||||
if (! $this->isAutoIncrementSequenceInSchema($fromSchema, $sequence)) {
|
||||
$createdSequences[] = $sequence;
|
||||
}
|
||||
} else {
|
||||
if ($this->diffSequence($sequence, $fromSchema->getSequence($sequenceName))) {
|
||||
$alteredSequences[] = $toSchema->getSequence($sequenceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($fromSchema->getSequences() as $sequence) {
|
||||
if ($this->isAutoIncrementSequenceInSchema($toSchema, $sequence)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sequenceName = $sequence->getShortestName($fromSchema->getName());
|
||||
|
||||
if ($toSchema->hasSequence($sequenceName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$droppedSequences[] = $sequence;
|
||||
}
|
||||
|
||||
$diff = new SchemaDiff(
|
||||
$createdTables,
|
||||
$alteredTables,
|
||||
$droppedTables,
|
||||
$fromSchema,
|
||||
$createdSchemas,
|
||||
$droppedSchemas,
|
||||
$createdSequences,
|
||||
$alteredSequences,
|
||||
$droppedSequences,
|
||||
);
|
||||
|
||||
$diff->orphanedForeignKeys = $orphanedForeignKeys;
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use non-static call to {@see compareSchemas()} instead.
|
||||
*
|
||||
* @return SchemaDiff
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function compare(Schema $fromSchema, Schema $toSchema)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4707',
|
||||
'Method compare() is deprecated. Use a non-static call to compareSchemas() instead.',
|
||||
);
|
||||
|
||||
return $this->compareSchemas($fromSchema, $toSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @param Sequence $sequence
|
||||
*/
|
||||
private function isAutoIncrementSequenceInSchema($schema, $sequence): bool
|
||||
{
|
||||
foreach ($schema->getTables() as $table) {
|
||||
if ($sequence->isAutoIncrementsFor($table)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function diffSequence(Sequence $sequence1, Sequence $sequence2)
|
||||
{
|
||||
if ($sequence1->getAllocationSize() !== $sequence2->getAllocationSize()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $sequence1->getInitialValue() !== $sequence2->getInitialValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the difference between the tables $fromTable and $toTable.
|
||||
*
|
||||
* If there are no differences this method returns the boolean false.
|
||||
*
|
||||
* @deprecated Use {@see compareTables()} and, optionally, {@see TableDiff::isEmpty()} instead.
|
||||
*
|
||||
* @return TableDiff|false
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function diffTable(Table $fromTable, Table $toTable)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5770',
|
||||
'%s is deprecated. Use compareTables() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
$diff = $this->compareTables($fromTable, $toTable);
|
||||
|
||||
if ($diff->isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the tables and returns the difference between them.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function compareTables(Table $fromTable, Table $toTable): TableDiff
|
||||
{
|
||||
$addedColumns = [];
|
||||
$modifiedColumns = [];
|
||||
$droppedColumns = [];
|
||||
$addedIndexes = [];
|
||||
$modifiedIndexes = [];
|
||||
$droppedIndexes = [];
|
||||
$addedForeignKeys = [];
|
||||
$modifiedForeignKeys = [];
|
||||
$droppedForeignKeys = [];
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
$addedColumns[$columnName] = $column;
|
||||
}
|
||||
|
||||
/* 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)) {
|
||||
$droppedColumns[$columnName] = $column;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$toColumn = $toTable->getColumn($columnName);
|
||||
|
||||
// See if column has changed properties in "to" table.
|
||||
$changedProperties = $this->diffColumn($column, $toColumn);
|
||||
|
||||
if ($this->platform !== null) {
|
||||
if ($this->columnsEqual($column, $toColumn)) {
|
||||
continue;
|
||||
}
|
||||
} elseif (count($changedProperties) === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$modifiedColumns[$column->getName()] = new ColumnDiff(
|
||||
$column->getName(),
|
||||
$toColumn,
|
||||
$changedProperties,
|
||||
$column,
|
||||
);
|
||||
}
|
||||
|
||||
$renamedColumns = $this->detectRenamedColumns($addedColumns, $droppedColumns);
|
||||
|
||||
$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->getPrimaryKey() !== null) || $fromTable->hasIndex($indexName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$addedIndexes[$indexName] = $index;
|
||||
}
|
||||
|
||||
/* 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->getPrimaryKey() === null) ||
|
||||
! $index->isPrimary() && ! $toTable->hasIndex($indexName)
|
||||
) {
|
||||
$droppedIndexes[$indexName] = $index;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
$modifiedIndexes[$indexName] = $toTableIndex;
|
||||
}
|
||||
|
||||
$renamedIndexes = $this->detectRenamedIndexes($addedIndexes, $droppedIndexes);
|
||||
|
||||
$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())) {
|
||||
$modifiedForeignKeys[] = $toConstraint;
|
||||
|
||||
unset($fromForeignKeys[$fromKey], $toForeignKeys[$toKey]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($fromForeignKeys as $fromConstraint) {
|
||||
$droppedForeignKeys[] = $fromConstraint;
|
||||
}
|
||||
|
||||
foreach ($toForeignKeys as $toConstraint) {
|
||||
$addedForeignKeys[] = $toConstraint;
|
||||
}
|
||||
|
||||
return new TableDiff(
|
||||
$toTable->getName(),
|
||||
$addedColumns,
|
||||
$modifiedColumns,
|
||||
$droppedColumns,
|
||||
$addedIndexes,
|
||||
$modifiedIndexes,
|
||||
$droppedIndexes,
|
||||
$fromTable,
|
||||
$addedForeignKeys,
|
||||
$modifiedForeignKeys,
|
||||
$droppedForeignKeys,
|
||||
$renamedColumns,
|
||||
$renamedIndexes,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param array<string,Column> $addedColumns
|
||||
* @param array<string,Column> $removedColumns
|
||||
*
|
||||
* @return array<string,Column>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function detectRenamedColumns(array &$addedColumns, array &$removedColumns): array
|
||||
{
|
||||
$candidatesByName = [];
|
||||
|
||||
foreach ($addedColumns as $addedColumnName => $addedColumn) {
|
||||
foreach ($removedColumns as $removedColumn) {
|
||||
if (! $this->columnsEqual($addedColumn, $removedColumn)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$candidatesByName[$addedColumn->getName()][] = [$removedColumn, $addedColumn, $addedColumnName];
|
||||
}
|
||||
}
|
||||
|
||||
$renamedColumns = [];
|
||||
|
||||
foreach ($candidatesByName as $candidates) {
|
||||
if (count($candidates) !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$removedColumn, $addedColumn] = $candidates[0];
|
||||
$removedColumnName = $removedColumn->getName();
|
||||
$addedColumnName = strtolower($addedColumn->getName());
|
||||
|
||||
if (isset($renamedColumns[$removedColumnName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$renamedColumns[$removedColumnName] = $addedColumn;
|
||||
unset(
|
||||
$addedColumns[$addedColumnName],
|
||||
$removedColumns[strtolower($removedColumnName)],
|
||||
);
|
||||
}
|
||||
|
||||
return $renamedColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param array<string,Index> $addedIndexes
|
||||
* @param array<string,Index> $removedIndexes
|
||||
*
|
||||
* @return array<string,Index>
|
||||
*/
|
||||
private function detectRenamedIndexes(array &$addedIndexes, array &$removedIndexes): array
|
||||
{
|
||||
$candidatesByName = [];
|
||||
|
||||
// Gather possible rename candidates by comparing each added and removed index based on semantics.
|
||||
foreach ($addedIndexes as $addedIndexName => $addedIndex) {
|
||||
foreach ($removedIndexes as $removedIndex) {
|
||||
if ($this->diffIndex($addedIndex, $removedIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$candidatesByName[$addedIndex->getName()][] = [$removedIndex, $addedIndex, $addedIndexName];
|
||||
}
|
||||
}
|
||||
|
||||
$renamedIndexes = [];
|
||||
|
||||
foreach ($candidatesByName as $candidates) {
|
||||
// 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($candidates) !== 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
[$removedIndex, $addedIndex] = $candidates[0];
|
||||
|
||||
$removedIndexName = strtolower($removedIndex->getName());
|
||||
$addedIndexName = strtolower($addedIndex->getName());
|
||||
|
||||
if (isset($renamedIndexes[$removedIndexName])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$renamedIndexes[$removedIndexName] = $addedIndex;
|
||||
unset(
|
||||
$addedIndexes[$addedIndexName],
|
||||
$removedIndexes[$removedIndexName],
|
||||
);
|
||||
}
|
||||
|
||||
return $renamedIndexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal The method should be only used from within the {@see Comparator} class hierarchy.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function diffForeignKey(ForeignKeyConstraint $key1, ForeignKeyConstraint $key2)
|
||||
{
|
||||
if (
|
||||
array_map('strtolower', $key1->getUnquotedLocalColumns())
|
||||
!== array_map('strtolower', $key2->getUnquotedLocalColumns())
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
array_map('strtolower', $key1->getUnquotedForeignColumns())
|
||||
!== array_map('strtolower', $key2->getUnquotedForeignColumns())
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($key1->getUnqualifiedForeignTableName() !== $key2->getUnqualifiedForeignTableName()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($key1->onUpdate() !== $key2->onUpdate()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $key1->onDelete() !== $key2->onDelete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the definitions of the given columns
|
||||
*
|
||||
* @internal The method should be only used from within the {@see Comparator} class hierarchy.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function columnsEqual(Column $column1, Column $column2): bool
|
||||
{
|
||||
if ($this->platform === null) {
|
||||
return $this->diffColumn($column1, $column2) === [];
|
||||
}
|
||||
|
||||
return $this->platform->columnsEqual($column1, $column2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the difference between the columns
|
||||
*
|
||||
* If there are differences this method returns the changed properties as a
|
||||
* string array, otherwise an empty array gets returned.
|
||||
*
|
||||
* @deprecated Use {@see columnsEqual()} instead.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function diffColumn(Column $column1, Column $column2)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5650',
|
||||
'%s is deprecated. Use diffTable() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
$properties1 = $column1->toArray();
|
||||
$properties2 = $column2->toArray();
|
||||
|
||||
$changedProperties = [];
|
||||
|
||||
if (get_class($properties1['type']) !== get_class($properties2['type'])) {
|
||||
$changedProperties[] = 'type';
|
||||
}
|
||||
|
||||
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;
|
||||
if ($length1 !== $length2) {
|
||||
$changedProperties[] = 'length';
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
$changedProperties[] = $key;
|
||||
}
|
||||
|
||||
return array_unique($changedProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the difference between the indexes $index1 and $index2.
|
||||
*
|
||||
* Compares $index1 with $index2 and returns true if there are any
|
||||
* differences or false in case there are no differences.
|
||||
*
|
||||
* @internal The method should be only used from within the {@see Comparator} class hierarchy.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function diffIndex(Index $index1, Index $index2)
|
||||
{
|
||||
return ! ($index1->isFulfilledBy($index2) && $index2->isFulfilledBy($index1));
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
|
||||
/**
|
||||
* Marker interface for constraints.
|
||||
*
|
||||
* @deprecated Use {@see ForeignKeyConstraint}, {@see Index} or {@see UniqueConstraint} instead.
|
||||
*/
|
||||
interface Constraint
|
||||
{
|
||||
/** @return string */
|
||||
public function getName();
|
||||
|
||||
/** @return string */
|
||||
public function getQuotedName(AbstractPlatform $platform);
|
||||
|
||||
/**
|
||||
* Returns the names of the referencing table columns
|
||||
* the constraint is associated with.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getColumns();
|
||||
|
||||
/**
|
||||
* Returns the quoted representation of the column names
|
||||
* the constraint is associated with.
|
||||
*
|
||||
* But only if they were defined with one or a column name
|
||||
* is a keyword reserved by the platform.
|
||||
* Otherwise the plain unquoted value as inserted is returned.
|
||||
*
|
||||
* @param AbstractPlatform $platform The platform to use for quotation.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getQuotedColumns(AbstractPlatform $platform);
|
||||
}
|
||||
@@ -0,0 +1,451 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\DB2Platform;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_change_key_case;
|
||||
use function implode;
|
||||
use function preg_match;
|
||||
use function str_replace;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function strtoupper;
|
||||
use function substr;
|
||||
|
||||
use const CASE_LOWER;
|
||||
|
||||
/**
|
||||
* IBM Db2 Schema Manager.
|
||||
*
|
||||
* @extends AbstractSchemaManager<DB2Platform>
|
||||
*/
|
||||
class DB2SchemaManager extends AbstractSchemaManager
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableNames()
|
||||
{
|
||||
return $this->doListTableNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTables()
|
||||
{
|
||||
return $this->doListTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see introspectTable()} instead.
|
||||
*/
|
||||
public function listTableDetails($name)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5595',
|
||||
'%s is deprecated. Use introspectTable() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return $this->doListTableDetails($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableColumns($table, $database = null)
|
||||
{
|
||||
return $this->doListTableColumns($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableIndexes($table)
|
||||
{
|
||||
return $this->doListTableIndexes($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableForeignKeys($table, $database = null)
|
||||
{
|
||||
return $this->doListTableForeignKeys($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function _getPortableTableColumnDefinition($tableColumn)
|
||||
{
|
||||
$tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
|
||||
|
||||
$length = null;
|
||||
$fixed = null;
|
||||
$scale = false;
|
||||
$precision = false;
|
||||
|
||||
$default = null;
|
||||
|
||||
if ($tableColumn['default'] !== null && $tableColumn['default'] !== 'NULL') {
|
||||
$default = $tableColumn['default'];
|
||||
|
||||
if (preg_match('/^\'(.*)\'$/s', $default, $matches) === 1) {
|
||||
$default = str_replace("''", "'", $matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
$type = $this->_platform->getDoctrineTypeMapping($tableColumn['typename']);
|
||||
|
||||
if (isset($tableColumn['comment'])) {
|
||||
$type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
$tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
}
|
||||
|
||||
switch (strtolower($tableColumn['typename'])) {
|
||||
case 'varchar':
|
||||
if ($tableColumn['codepage'] === 0) {
|
||||
$type = Types::BINARY;
|
||||
}
|
||||
|
||||
$length = $tableColumn['length'];
|
||||
$fixed = false;
|
||||
break;
|
||||
|
||||
case 'character':
|
||||
if ($tableColumn['codepage'] === 0) {
|
||||
$type = Types::BINARY;
|
||||
}
|
||||
|
||||
$length = $tableColumn['length'];
|
||||
$fixed = true;
|
||||
break;
|
||||
|
||||
case 'clob':
|
||||
$length = $tableColumn['length'];
|
||||
break;
|
||||
|
||||
case 'decimal':
|
||||
case 'double':
|
||||
case 'real':
|
||||
$scale = $tableColumn['scale'];
|
||||
$precision = $tableColumn['length'];
|
||||
break;
|
||||
}
|
||||
|
||||
$options = [
|
||||
'length' => $length,
|
||||
'fixed' => (bool) $fixed,
|
||||
'default' => $default,
|
||||
'autoincrement' => (bool) $tableColumn['autoincrement'],
|
||||
'notnull' => $tableColumn['nulls'] === 'N',
|
||||
'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== ''
|
||||
? $tableColumn['comment']
|
||||
: null,
|
||||
];
|
||||
|
||||
if ($scale !== null && $precision !== null) {
|
||||
$options['scale'] = $scale;
|
||||
$options['precision'] = $precision;
|
||||
}
|
||||
|
||||
return new Column($tableColumn['colname'], Type::getType($type), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableDefinition($table)
|
||||
{
|
||||
$table = array_change_key_case($table, CASE_LOWER);
|
||||
|
||||
return $table['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
|
||||
{
|
||||
foreach ($tableIndexes as &$tableIndexRow) {
|
||||
$tableIndexRow = array_change_key_case($tableIndexRow, CASE_LOWER);
|
||||
$tableIndexRow['primary'] = (bool) $tableIndexRow['primary'];
|
||||
}
|
||||
|
||||
return parent::_getPortableTableIndexesList($tableIndexes, $tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
|
||||
{
|
||||
return new ForeignKeyConstraint(
|
||||
$tableForeignKey['local_columns'],
|
||||
$tableForeignKey['foreign_table'],
|
||||
$tableForeignKey['foreign_columns'],
|
||||
$tableForeignKey['name'],
|
||||
$tableForeignKey['options'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeysList($tableForeignKeys)
|
||||
{
|
||||
$foreignKeys = [];
|
||||
|
||||
foreach ($tableForeignKeys as $tableForeignKey) {
|
||||
$tableForeignKey = array_change_key_case($tableForeignKey, CASE_LOWER);
|
||||
|
||||
if (! isset($foreignKeys[$tableForeignKey['index_name']])) {
|
||||
$foreignKeys[$tableForeignKey['index_name']] = [
|
||||
'local_columns' => [$tableForeignKey['local_column']],
|
||||
'foreign_table' => $tableForeignKey['foreign_table'],
|
||||
'foreign_columns' => [$tableForeignKey['foreign_column']],
|
||||
'name' => $tableForeignKey['index_name'],
|
||||
'options' => [
|
||||
'onUpdate' => $tableForeignKey['on_update'],
|
||||
'onDelete' => $tableForeignKey['on_delete'],
|
||||
],
|
||||
];
|
||||
} else {
|
||||
$foreignKeys[$tableForeignKey['index_name']]['local_columns'][] = $tableForeignKey['local_column'];
|
||||
$foreignKeys[$tableForeignKey['index_name']]['foreign_columns'][] = $tableForeignKey['foreign_column'];
|
||||
}
|
||||
}
|
||||
|
||||
return parent::_getPortableTableForeignKeysList($foreignKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $def
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
protected function _getPortableForeignKeyRuleDef($def)
|
||||
{
|
||||
if ($def === 'C') {
|
||||
return 'CASCADE';
|
||||
}
|
||||
|
||||
if ($def === 'N') {
|
||||
return 'SET NULL';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableViewDefinition($view)
|
||||
{
|
||||
$view = array_change_key_case($view, CASE_LOWER);
|
||||
|
||||
$sql = '';
|
||||
$pos = strpos($view['text'], ' AS ');
|
||||
|
||||
if ($pos !== false) {
|
||||
$sql = substr($view['text'], $pos + 4);
|
||||
}
|
||||
|
||||
return new View($view['name'], $sql);
|
||||
}
|
||||
|
||||
protected function normalizeName(string $name): string
|
||||
{
|
||||
$identifier = new Identifier($name);
|
||||
|
||||
return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name);
|
||||
}
|
||||
|
||||
protected function selectTableNames(string $databaseName): Result
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT NAME
|
||||
FROM SYSIBM.SYSTABLES
|
||||
WHERE TYPE = 'T'
|
||||
AND CREATOR = ?
|
||||
SQL;
|
||||
|
||||
return $this->_conn->executeQuery($sql, [$databaseName]);
|
||||
}
|
||||
|
||||
protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' C.TABNAME AS NAME,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
C.COLNAME,
|
||||
C.TYPENAME,
|
||||
C.CODEPAGE,
|
||||
C.NULLS,
|
||||
C.LENGTH,
|
||||
C.SCALE,
|
||||
C.REMARKS AS COMMENT,
|
||||
CASE
|
||||
WHEN C.GENERATED = 'D' THEN 1
|
||||
ELSE 0
|
||||
END AS AUTOINCREMENT,
|
||||
C.DEFAULT
|
||||
FROM SYSCAT.COLUMNS C
|
||||
JOIN SYSCAT.TABLES AS T
|
||||
ON T.TABSCHEMA = C.TABSCHEMA
|
||||
AND T.TABNAME = C.TABNAME
|
||||
SQL;
|
||||
|
||||
$conditions = ['C.TABSCHEMA = ?', "T.TYPE = 'T'"];
|
||||
$params = [$databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'C.TABNAME = ?';
|
||||
$params[] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.TABNAME, C.COLNO';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' IDX.TABNAME AS NAME,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
IDX.INDNAME AS KEY_NAME,
|
||||
IDXCOL.COLNAME AS COLUMN_NAME,
|
||||
CASE
|
||||
WHEN IDX.UNIQUERULE = 'P' THEN 1
|
||||
ELSE 0
|
||||
END AS PRIMARY,
|
||||
CASE
|
||||
WHEN IDX.UNIQUERULE = 'D' THEN 1
|
||||
ELSE 0
|
||||
END AS NON_UNIQUE
|
||||
FROM SYSCAT.INDEXES AS IDX
|
||||
JOIN SYSCAT.TABLES AS T
|
||||
ON IDX.TABSCHEMA = T.TABSCHEMA AND IDX.TABNAME = T.TABNAME
|
||||
JOIN SYSCAT.INDEXCOLUSE AS IDXCOL
|
||||
ON IDX.INDSCHEMA = IDXCOL.INDSCHEMA AND IDX.INDNAME = IDXCOL.INDNAME
|
||||
SQL;
|
||||
|
||||
$conditions = ['IDX.TABSCHEMA = ?', "T.TYPE = 'T'"];
|
||||
$params = [$databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'IDX.TABNAME = ?';
|
||||
$params[] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IDX.INDNAME, IDXCOL.COLSEQ';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' R.TABNAME AS NAME,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
FKCOL.COLNAME AS LOCAL_COLUMN,
|
||||
R.REFTABNAME AS FOREIGN_TABLE,
|
||||
PKCOL.COLNAME AS FOREIGN_COLUMN,
|
||||
R.CONSTNAME AS INDEX_NAME,
|
||||
CASE
|
||||
WHEN R.UPDATERULE = 'R' THEN 'RESTRICT'
|
||||
END AS ON_UPDATE,
|
||||
CASE
|
||||
WHEN R.DELETERULE = 'C' THEN 'CASCADE'
|
||||
WHEN R.DELETERULE = 'N' THEN 'SET NULL'
|
||||
WHEN R.DELETERULE = 'R' THEN 'RESTRICT'
|
||||
END AS ON_DELETE
|
||||
FROM SYSCAT.REFERENCES AS R
|
||||
JOIN SYSCAT.TABLES AS T
|
||||
ON T.TABSCHEMA = R.TABSCHEMA
|
||||
AND T.TABNAME = R.TABNAME
|
||||
JOIN SYSCAT.KEYCOLUSE AS FKCOL
|
||||
ON FKCOL.CONSTNAME = R.CONSTNAME
|
||||
AND FKCOL.TABSCHEMA = R.TABSCHEMA
|
||||
AND FKCOL.TABNAME = R.TABNAME
|
||||
JOIN SYSCAT.KEYCOLUSE AS PKCOL
|
||||
ON PKCOL.CONSTNAME = R.REFKEYNAME
|
||||
AND PKCOL.TABSCHEMA = R.REFTABSCHEMA
|
||||
AND PKCOL.TABNAME = R.REFTABNAME
|
||||
AND PKCOL.COLSEQ = FKCOL.COLSEQ
|
||||
SQL;
|
||||
|
||||
$conditions = ['R.TABSCHEMA = ?', "T.TYPE = 'T'"];
|
||||
$params = [$databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'R.TABNAME = ?';
|
||||
$params[] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY R.CONSTNAME, FKCOL.COLSEQ';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
|
||||
{
|
||||
$sql = 'SELECT NAME, REMARKS';
|
||||
|
||||
$conditions = [];
|
||||
$params = [];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'NAME = ?';
|
||||
$params[] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' FROM SYSIBM.SYSTABLES';
|
||||
|
||||
if ($conditions !== []) {
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions);
|
||||
}
|
||||
|
||||
/** @var array<string,array<string,mixed>> $metadata */
|
||||
$metadata = $this->_conn->executeQuery($sql, $params)
|
||||
->fetchAllAssociativeIndexed();
|
||||
|
||||
$tableOptions = [];
|
||||
foreach ($metadata as $table => $data) {
|
||||
$data = array_change_key_case($data, CASE_LOWER);
|
||||
|
||||
$tableOptions[$table] = ['comment' => $data['remarks']];
|
||||
}
|
||||
|
||||
return $tableOptions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Exception;
|
||||
|
||||
/**
|
||||
* A schema manager factory that returns the default schema manager for the given platform.
|
||||
*/
|
||||
final class DefaultSchemaManagerFactory implements SchemaManagerFactory
|
||||
{
|
||||
/** @throws Exception If the platform does not support creating schema managers yet. */
|
||||
public function createSchemaManager(Connection $connection): AbstractSchemaManager
|
||||
{
|
||||
return $connection->getDatabasePlatform()->createSchemaManager($connection);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Exception;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
final class ColumnAlreadyExists extends SchemaException
|
||||
{
|
||||
public static function new(string $tableName, string $columnName): self
|
||||
{
|
||||
return new self(
|
||||
sprintf('The column "%s" on table "%s" already exists.', $columnName, $tableName),
|
||||
self::COLUMN_ALREADY_EXISTS,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Exception;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
final class ColumnDoesNotExist extends SchemaException
|
||||
{
|
||||
public static function new(string $columnName, string $table): self
|
||||
{
|
||||
return new self(
|
||||
sprintf('There is no column with name "%s" on table "%s".', $columnName, $table),
|
||||
self::COLUMN_DOESNT_EXIST,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Exception;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
final class ForeignKeyDoesNotExist extends SchemaException
|
||||
{
|
||||
public static function new(string $foreignKeyName, string $table): self
|
||||
{
|
||||
return new self(
|
||||
sprintf('There exists no foreign key with the name "%s" on table "%s".', $foreignKeyName, $table),
|
||||
self::FOREIGNKEY_DOESNT_EXIST,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Exception;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
final class IndexAlreadyExists extends SchemaException
|
||||
{
|
||||
public static function new(string $indexName, string $table): self
|
||||
{
|
||||
return new self(
|
||||
sprintf('An index with name "%s" was already defined on table "%s".', $indexName, $table),
|
||||
self::INDEX_ALREADY_EXISTS,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Exception;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
final class IndexDoesNotExist extends SchemaException
|
||||
{
|
||||
public static function new(string $indexName, string $table): self
|
||||
{
|
||||
return new self(
|
||||
sprintf('Index "%s" does not exist on table "%s".', $indexName, $table),
|
||||
self::INDEX_DOESNT_EXIST,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Exception;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
final class IndexNameInvalid extends SchemaException
|
||||
{
|
||||
public static function new(string $indexName): self
|
||||
{
|
||||
return new self(
|
||||
sprintf('Invalid index name "%s" given, has to be [a-zA-Z0-9_].', $indexName),
|
||||
self::INDEX_INVALID_NAME,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Exception;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
final class InvalidTableName extends SchemaException
|
||||
{
|
||||
public static function new(string $tableName): self
|
||||
{
|
||||
return new self(sprintf('Invalid table name specified "%s".', $tableName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Exception;
|
||||
|
||||
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
|
||||
use function implode;
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
final class NamedForeignKeyRequired extends SchemaException
|
||||
{
|
||||
public static function new(Table $localTable, ForeignKeyConstraint $foreignKey): self
|
||||
{
|
||||
return new self(
|
||||
sprintf(
|
||||
'The performed schema operation on "%s" requires a named foreign key, ' .
|
||||
'but the given foreign key from (%s) onto foreign table "%s" (%s) is currently unnamed.',
|
||||
$localTable->getName(),
|
||||
implode(', ', $foreignKey->getColumns()),
|
||||
$foreignKey->getForeignTableName(),
|
||||
implode(', ', $foreignKey->getForeignColumns()),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Exception;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
final class NamespaceAlreadyExists extends SchemaException
|
||||
{
|
||||
public static function new(string $namespaceName): self
|
||||
{
|
||||
return new self(
|
||||
sprintf('The namespace with name "%s" already exists.', $namespaceName),
|
||||
self::NAMESPACE_ALREADY_EXISTS,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Exception;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
final class SequenceAlreadyExists extends SchemaException
|
||||
{
|
||||
public static function new(string $sequenceName): self
|
||||
{
|
||||
return new self(
|
||||
sprintf('The sequence "%s" already exists.', $sequenceName),
|
||||
self::SEQUENCE_ALREADY_EXISTS,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Exception;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
final class SequenceDoesNotExist extends SchemaException
|
||||
{
|
||||
public static function new(string $sequenceName): self
|
||||
{
|
||||
return new self(
|
||||
sprintf('There exists no sequence with the name "%s".', $sequenceName),
|
||||
self::SEQUENCE_DOENST_EXIST,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Exception;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
final class TableAlreadyExists extends SchemaException
|
||||
{
|
||||
public static function new(string $tableName): self
|
||||
{
|
||||
return new self(
|
||||
sprintf('The table with name "%s" already exists.', $tableName),
|
||||
self::TABLE_ALREADY_EXISTS,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Exception;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
final class TableDoesNotExist extends SchemaException
|
||||
{
|
||||
public static function new(string $tableName): self
|
||||
{
|
||||
return new self(
|
||||
sprintf('There is no table with name "%s" in the schema.', $tableName),
|
||||
self::TABLE_DOESNT_EXIST,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Exception;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
final class UniqueConstraintDoesNotExist extends SchemaException
|
||||
{
|
||||
public static function new(string $constraintName, string $table): self
|
||||
{
|
||||
return new self(
|
||||
sprintf('There exists no unique constraint with the name "%s" on table "%s".', $constraintName, $table),
|
||||
self::CONSTRAINT_DOESNT_EXIST,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Exception;
|
||||
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
final class UnknownColumnOption extends SchemaException
|
||||
{
|
||||
public static function new(string $name): self
|
||||
{
|
||||
return new self(
|
||||
sprintf('The "%s" column option is not supported.', $name),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function strrpos;
|
||||
use function strtolower;
|
||||
use function strtoupper;
|
||||
use function substr;
|
||||
|
||||
/**
|
||||
* An abstraction class for a foreign key constraint.
|
||||
*/
|
||||
class ForeignKeyConstraint extends AbstractAsset implements Constraint
|
||||
{
|
||||
/**
|
||||
* Instance of the referencing table the foreign key constraint is associated with.
|
||||
*
|
||||
* @var Table
|
||||
*/
|
||||
protected $_localTable;
|
||||
|
||||
/**
|
||||
* Asset identifier instances of the referencing table column names the foreign key constraint is associated with.
|
||||
* array($columnName => Identifier)
|
||||
*
|
||||
* @var Identifier[]
|
||||
*/
|
||||
protected $_localColumnNames;
|
||||
|
||||
/**
|
||||
* Table or asset identifier instance of the referenced table name the foreign key constraint is associated with.
|
||||
*
|
||||
* @var Table|Identifier
|
||||
*/
|
||||
protected $_foreignTableName;
|
||||
|
||||
/**
|
||||
* Asset identifier instances of the referenced table column names the foreign key constraint is associated with.
|
||||
* array($columnName => Identifier)
|
||||
*
|
||||
* @var Identifier[]
|
||||
*/
|
||||
protected $_foreignColumnNames;
|
||||
|
||||
/**
|
||||
* Options associated with the foreign key constraint.
|
||||
*
|
||||
* @var mixed[]
|
||||
*/
|
||||
protected $_options;
|
||||
|
||||
/**
|
||||
* Initializes the foreign key constraint.
|
||||
*
|
||||
* @param string[] $localColumnNames Names of the referencing table columns.
|
||||
* @param Table|string $foreignTableName Referenced table.
|
||||
* @param string[] $foreignColumnNames Names of the referenced table columns.
|
||||
* @param string|null $name Name of the foreign key constraint.
|
||||
* @param mixed[] $options Options associated with the foreign key constraint.
|
||||
*/
|
||||
public function __construct(
|
||||
array $localColumnNames,
|
||||
$foreignTableName,
|
||||
array $foreignColumnNames,
|
||||
$name = null,
|
||||
array $options = []
|
||||
) {
|
||||
if ($name !== null) {
|
||||
$this->_setName($name);
|
||||
}
|
||||
|
||||
$this->_localColumnNames = $this->createIdentifierMap($localColumnNames);
|
||||
|
||||
if ($foreignTableName instanceof Table) {
|
||||
$this->_foreignTableName = $foreignTableName;
|
||||
} else {
|
||||
$this->_foreignTableName = new Identifier($foreignTableName);
|
||||
}
|
||||
|
||||
$this->_foreignColumnNames = $this->createIdentifierMap($foreignColumnNames);
|
||||
$this->_options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $names
|
||||
*
|
||||
* @return Identifier[]
|
||||
*/
|
||||
private function createIdentifierMap(array $names): array
|
||||
{
|
||||
$identifiers = [];
|
||||
|
||||
foreach ($names as $name) {
|
||||
$identifiers[$name] = new Identifier($name);
|
||||
}
|
||||
|
||||
return $identifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the referencing table
|
||||
* the foreign key constraint is associated with.
|
||||
*
|
||||
* @deprecated Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalTableName()
|
||||
{
|
||||
return $this->_localTable->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Table instance of the referencing table
|
||||
* the foreign key constraint is associated with.
|
||||
*
|
||||
* @deprecated Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead.
|
||||
*
|
||||
* @param Table $table Instance of the referencing table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setLocalTable(Table $table)
|
||||
{
|
||||
$this->_localTable = $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use the table that contains the foreign key as part of its {@see Table::$_fkConstraints} instead.
|
||||
*
|
||||
* @return Table
|
||||
*/
|
||||
public function getLocalTable()
|
||||
{
|
||||
return $this->_localTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of the referencing table columns
|
||||
* the foreign key constraint is associated with.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getLocalColumns()
|
||||
{
|
||||
return array_keys($this->_localColumnNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quoted representation of the referencing table column names
|
||||
* the foreign key constraint is associated with.
|
||||
*
|
||||
* But only if they were defined with one or the referencing table column name
|
||||
* is a keyword reserved by the platform.
|
||||
* Otherwise the plain unquoted value as inserted is returned.
|
||||
*
|
||||
* @param AbstractPlatform $platform The platform to use for quotation.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getQuotedLocalColumns(AbstractPlatform $platform)
|
||||
{
|
||||
$columns = [];
|
||||
|
||||
foreach ($this->_localColumnNames as $column) {
|
||||
$columns[] = $column->getQuotedName($platform);
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns unquoted representation of local table column names for comparison with other FK
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getUnquotedLocalColumns()
|
||||
{
|
||||
return array_map([$this, 'trimQuotes'], $this->getLocalColumns());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns unquoted representation of foreign table column names for comparison with other FK
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getUnquotedForeignColumns()
|
||||
{
|
||||
return array_map([$this, 'trimQuotes'], $this->getForeignColumns());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see getLocalColumns()} instead.
|
||||
*
|
||||
* @see getLocalColumns
|
||||
*/
|
||||
public function getColumns()
|
||||
{
|
||||
return $this->getLocalColumns();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quoted representation of the referencing table column names
|
||||
* the foreign key constraint is associated with.
|
||||
*
|
||||
* But only if they were defined with one or the referencing table column name
|
||||
* is a keyword reserved by the platform.
|
||||
* Otherwise the plain unquoted value as inserted is returned.
|
||||
*
|
||||
* @deprecated Use {@see getQuotedLocalColumns()} instead.
|
||||
*
|
||||
* @see getQuotedLocalColumns
|
||||
*
|
||||
* @param AbstractPlatform $platform The platform to use for quotation.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getQuotedColumns(AbstractPlatform $platform)
|
||||
{
|
||||
return $this->getQuotedLocalColumns($platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the referenced table
|
||||
* the foreign key constraint is associated with.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getForeignTableName()
|
||||
{
|
||||
return $this->_foreignTableName->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the non-schema qualified foreign table name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUnqualifiedForeignTableName()
|
||||
{
|
||||
$name = $this->_foreignTableName->getName();
|
||||
$position = strrpos($name, '.');
|
||||
|
||||
if ($position !== false) {
|
||||
$name = substr($name, $position + 1);
|
||||
}
|
||||
|
||||
return strtolower($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quoted representation of the referenced table name
|
||||
* the foreign key constraint is associated with.
|
||||
*
|
||||
* But only if it was defined with one or the referenced table name
|
||||
* is a keyword reserved by the platform.
|
||||
* Otherwise the plain unquoted value as inserted is returned.
|
||||
*
|
||||
* @param AbstractPlatform $platform The platform to use for quotation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getQuotedForeignTableName(AbstractPlatform $platform)
|
||||
{
|
||||
return $this->_foreignTableName->getQuotedName($platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of the referenced table columns
|
||||
* the foreign key constraint is associated with.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getForeignColumns()
|
||||
{
|
||||
return array_keys($this->_foreignColumnNames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quoted representation of the referenced table column names
|
||||
* the foreign key constraint is associated with.
|
||||
*
|
||||
* But only if they were defined with one or the referenced table column name
|
||||
* is a keyword reserved by the platform.
|
||||
* Otherwise the plain unquoted value as inserted is returned.
|
||||
*
|
||||
* @param AbstractPlatform $platform The platform to use for quotation.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getQuotedForeignColumns(AbstractPlatform $platform)
|
||||
{
|
||||
$columns = [];
|
||||
|
||||
foreach ($this->_foreignColumnNames as $column) {
|
||||
$columns[] = $column->getQuotedName($platform);
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not a given option
|
||||
* is associated with the foreign key constraint.
|
||||
*
|
||||
* @param string $name Name of the option to check.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOption($name)
|
||||
{
|
||||
return isset($this->_options[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an option associated with the foreign key constraint.
|
||||
*
|
||||
* @param string $name Name of the option the foreign key constraint is associated with.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOption($name)
|
||||
{
|
||||
return $this->_options[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the options associated with the foreign key constraint.
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the referential action for UPDATE operations
|
||||
* on the referenced table the foreign key constraint is associated with.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function onUpdate()
|
||||
{
|
||||
return $this->onEvent('onUpdate');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the referential action for DELETE operations
|
||||
* on the referenced table the foreign key constraint is associated with.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function onDelete()
|
||||
{
|
||||
return $this->onEvent('onDelete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the referential action for a given database operation
|
||||
* on the referenced table the foreign key constraint is associated with.
|
||||
*
|
||||
* @param string $event Name of the database operation/event to return the referential action for.
|
||||
*/
|
||||
private function onEvent($event): ?string
|
||||
{
|
||||
if (isset($this->_options[$event])) {
|
||||
$onEvent = strtoupper($this->_options[$event]);
|
||||
|
||||
if ($onEvent !== 'NO ACTION' && $onEvent !== 'RESTRICT') {
|
||||
return $onEvent;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether this foreign key constraint intersects the given index columns.
|
||||
*
|
||||
* Returns `true` if at least one of this foreign key's local columns
|
||||
* matches one of the given index's columns, `false` otherwise.
|
||||
*
|
||||
* @param Index $index The index to be checked against.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function intersectsIndexColumns(Index $index)
|
||||
{
|
||||
foreach ($index->getColumns() as $indexColumn) {
|
||||
foreach ($this->_localColumnNames as $localColumn) {
|
||||
if (strtolower($indexColumn) === strtolower($localColumn->getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
/**
|
||||
* An abstraction class for an asset identifier.
|
||||
*
|
||||
* Wraps identifier names like column names in indexes / foreign keys
|
||||
* in an abstract class for proper quotation capabilities.
|
||||
*/
|
||||
class Identifier extends AbstractAsset
|
||||
{
|
||||
/**
|
||||
* @param string $identifier Identifier name to wrap.
|
||||
* @param bool $quote Whether to force quoting the given identifier.
|
||||
*/
|
||||
public function __construct($identifier, $quote = false)
|
||||
{
|
||||
$this->_setName($identifier);
|
||||
|
||||
if (! $quote || $this->_quoted) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->_setName('"' . $this->getName() . '"');
|
||||
}
|
||||
}
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use InvalidArgumentException;
|
||||
|
||||
use function array_filter;
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function array_search;
|
||||
use function array_shift;
|
||||
use function count;
|
||||
use function strtolower;
|
||||
|
||||
class Index extends AbstractAsset implements Constraint
|
||||
{
|
||||
/**
|
||||
* Asset identifier instances of the column names the index is associated with.
|
||||
* array($columnName => Identifier)
|
||||
*
|
||||
* @var Identifier[]
|
||||
*/
|
||||
protected $_columns = [];
|
||||
|
||||
/** @var bool */
|
||||
protected $_isUnique = false;
|
||||
|
||||
/** @var bool */
|
||||
protected $_isPrimary = false;
|
||||
|
||||
/**
|
||||
* Platform specific flags for indexes.
|
||||
* array($flagName => true)
|
||||
*
|
||||
* @var true[]
|
||||
*/
|
||||
protected $_flags = [];
|
||||
|
||||
/**
|
||||
* Platform specific options
|
||||
*
|
||||
* @todo $_flags should eventually be refactored into options
|
||||
* @var mixed[]
|
||||
*/
|
||||
private array $options = [];
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string[] $columns
|
||||
* @param bool $isUnique
|
||||
* @param bool $isPrimary
|
||||
* @param string[] $flags
|
||||
* @param mixed[] $options
|
||||
*/
|
||||
public function __construct(
|
||||
$name,
|
||||
array $columns,
|
||||
$isUnique = false,
|
||||
$isPrimary = false,
|
||||
array $flags = [],
|
||||
array $options = []
|
||||
) {
|
||||
$isUnique = $isUnique || $isPrimary;
|
||||
|
||||
$this->_setName($name);
|
||||
$this->_isUnique = $isUnique;
|
||||
$this->_isPrimary = $isPrimary;
|
||||
$this->options = $options;
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$this->_addColumn($column);
|
||||
}
|
||||
|
||||
foreach ($flags as $flag) {
|
||||
$this->addFlag($flag);
|
||||
}
|
||||
}
|
||||
|
||||
/** @throws InvalidArgumentException */
|
||||
protected function _addColumn(string $column): void
|
||||
{
|
||||
$this->_columns[$column] = new Identifier($column);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getColumns()
|
||||
{
|
||||
return array_keys($this->_columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getQuotedColumns(AbstractPlatform $platform)
|
||||
{
|
||||
$subParts = $platform->supportsColumnLengthIndexes() && $this->hasOption('lengths')
|
||||
? $this->getOption('lengths') : [];
|
||||
|
||||
$columns = [];
|
||||
|
||||
foreach ($this->_columns as $column) {
|
||||
$length = array_shift($subParts);
|
||||
|
||||
$quotedColumn = $column->getQuotedName($platform);
|
||||
|
||||
if ($length !== null) {
|
||||
$quotedColumn .= '(' . $length . ')';
|
||||
}
|
||||
|
||||
$columns[] = $quotedColumn;
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public function getUnquotedColumns()
|
||||
{
|
||||
return array_map([$this, 'trimQuotes'], $this->getColumns());
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the index neither unique nor primary key?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSimpleIndex()
|
||||
{
|
||||
return ! $this->_isPrimary && ! $this->_isUnique;
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isUnique()
|
||||
{
|
||||
return $this->_isUnique;
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isPrimary()
|
||||
{
|
||||
return $this->_isPrimary;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param int $pos
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasColumnAtPosition($name, $pos = 0)
|
||||
{
|
||||
$name = $this->trimQuotes(strtolower($name));
|
||||
$indexColumns = array_map('strtolower', $this->getUnquotedColumns());
|
||||
|
||||
return array_search($name, $indexColumns, true) === $pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this index exactly spans the given column names in the correct order.
|
||||
*
|
||||
* @param string[] $columnNames
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function spansColumns(array $columnNames)
|
||||
{
|
||||
$columns = $this->getColumns();
|
||||
$numberOfColumns = count($columns);
|
||||
$sameColumns = true;
|
||||
|
||||
for ($i = 0; $i < $numberOfColumns; $i++) {
|
||||
if (
|
||||
isset($columnNames[$i])
|
||||
&& $this->trimQuotes(strtolower($columns[$i])) === $this->trimQuotes(strtolower($columnNames[$i]))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sameColumns = false;
|
||||
}
|
||||
|
||||
return $sameColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keeping misspelled function name for backwards compatibility
|
||||
*
|
||||
* @deprecated Use {@see isFulfilledBy()} instead.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isFullfilledBy(Index $other)
|
||||
{
|
||||
return $this->isFulfilledBy($other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the other index already fulfills all the indexing and constraint needs of the current one.
|
||||
*/
|
||||
public function isFulfilledBy(Index $other): bool
|
||||
{
|
||||
// allow the other index to be equally large only. It being larger is an option
|
||||
// but it creates a problem with scenarios of the kind PRIMARY KEY(foo,bar) UNIQUE(foo)
|
||||
if (count($other->getColumns()) !== count($this->getColumns())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if columns are the same, and even in the same order
|
||||
$sameColumns = $this->spansColumns($other->getColumns());
|
||||
|
||||
if ($sameColumns) {
|
||||
if (! $this->samePartialIndex($other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->hasSameColumnLengths($other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->isUnique() && ! $this->isPrimary()) {
|
||||
// this is a special case: If the current key is neither primary or unique, any unique or
|
||||
// primary key will always have the same effect for the index and there cannot be any constraint
|
||||
// overlaps. This means a primary or unique index can always fulfill the requirements of just an
|
||||
// index that has no constraints.
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($other->isPrimary() !== $this->isPrimary()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $other->isUnique() === $this->isUnique();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if the other index is a non-unique, non primary index that can be overwritten by this one.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function overrules(Index $other)
|
||||
{
|
||||
if ($other->isPrimary()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->isSimpleIndex() && $other->isUnique()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->spansColumns($other->getColumns())
|
||||
&& ($this->isPrimary() || $this->isUnique())
|
||||
&& $this->samePartialIndex($other);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns platform specific flags for indexes.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getFlags()
|
||||
{
|
||||
return array_keys($this->_flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Flag for an index that translates to platform specific handling.
|
||||
*
|
||||
* @param string $flag
|
||||
*
|
||||
* @return Index
|
||||
*
|
||||
* @example $index->addFlag('CLUSTERED')
|
||||
*/
|
||||
public function addFlag($flag)
|
||||
{
|
||||
$this->_flags[strtolower($flag)] = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this index have a specific flag?
|
||||
*
|
||||
* @param string $flag
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasFlag($flag)
|
||||
{
|
||||
return isset($this->_flags[strtolower($flag)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a flag.
|
||||
*
|
||||
* @param string $flag
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeFlag($flag)
|
||||
{
|
||||
unset($this->_flags[strtolower($flag)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasOption($name)
|
||||
{
|
||||
return isset($this->options[strtolower($name)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOption($name)
|
||||
{
|
||||
return $this->options[strtolower($name)];
|
||||
}
|
||||
|
||||
/** @return mixed[] */
|
||||
public function getOptions()
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the two indexes have the same partial index
|
||||
*/
|
||||
private function samePartialIndex(Index $other): bool
|
||||
{
|
||||
if (
|
||||
$this->hasOption('where')
|
||||
&& $other->hasOption('where')
|
||||
&& $this->getOption('where') === $other->getOption('where')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ! $this->hasOption('where') && ! $other->hasOption('where');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the index has the same column lengths as the other
|
||||
*/
|
||||
private function hasSameColumnLengths(self $other): bool
|
||||
{
|
||||
$filter = static function (?int $length): bool {
|
||||
return $length !== null;
|
||||
};
|
||||
|
||||
return array_filter($this->options['lengths'] ?? [], $filter)
|
||||
=== array_filter($other->options['lengths'] ?? [], $filter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
|
||||
/** @internal Will be removed in 4.0. */
|
||||
final class LegacySchemaManagerFactory implements SchemaManagerFactory
|
||||
{
|
||||
public function createSchemaManager(Connection $connection): AbstractSchemaManager
|
||||
{
|
||||
return $connection->getDriver()->getSchemaManager(
|
||||
$connection,
|
||||
$connection->getDatabasePlatform(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,623 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
|
||||
use Doctrine\DBAL\Platforms\MariaDb1027Platform;
|
||||
use Doctrine\DBAL\Platforms\MySQL;
|
||||
use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider\CachingCollationMetadataProvider;
|
||||
use Doctrine\DBAL\Platforms\MySQL\CollationMetadataProvider\ConnectionCollationMetadataProvider;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_change_key_case;
|
||||
use function array_shift;
|
||||
use function assert;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function is_string;
|
||||
use function preg_match;
|
||||
use function strpos;
|
||||
use function strtok;
|
||||
use function strtolower;
|
||||
use function strtr;
|
||||
|
||||
use const CASE_LOWER;
|
||||
|
||||
/**
|
||||
* Schema manager for the MySQL RDBMS.
|
||||
*
|
||||
* @extends AbstractSchemaManager<AbstractMySQLPlatform>
|
||||
*/
|
||||
class MySQLSchemaManager extends AbstractSchemaManager
|
||||
{
|
||||
/** @see https://mariadb.com/kb/en/library/string-literals/#escape-sequences */
|
||||
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}
|
||||
*/
|
||||
public function listTableNames()
|
||||
{
|
||||
return $this->doListTableNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTables()
|
||||
{
|
||||
return $this->doListTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see introspectTable()} instead.
|
||||
*/
|
||||
public function listTableDetails($name)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5595',
|
||||
'%s is deprecated. Use introspectTable() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return $this->doListTableDetails($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableColumns($table, $database = null)
|
||||
{
|
||||
return $this->doListTableColumns($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableIndexes($table)
|
||||
{
|
||||
return $this->doListTableIndexes($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableForeignKeys($table, $database = null)
|
||||
{
|
||||
return $this->doListTableForeignKeys($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableViewDefinition($view)
|
||||
{
|
||||
return new View($view['TABLE_NAME'], $view['VIEW_DEFINITION']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableDefinition($table)
|
||||
{
|
||||
return array_shift($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
|
||||
{
|
||||
foreach ($tableIndexes as $k => $v) {
|
||||
$v = array_change_key_case($v, CASE_LOWER);
|
||||
if ($v['key_name'] === 'PRIMARY') {
|
||||
$v['primary'] = true;
|
||||
} else {
|
||||
$v['primary'] = false;
|
||||
}
|
||||
|
||||
if (strpos($v['index_type'], 'FULLTEXT') !== false) {
|
||||
$v['flags'] = ['FULLTEXT'];
|
||||
} elseif (strpos($v['index_type'], 'SPATIAL') !== false) {
|
||||
$v['flags'] = ['SPATIAL'];
|
||||
}
|
||||
|
||||
// Ignore prohibited prefix `length` for spatial index
|
||||
if (strpos($v['index_type'], 'SPATIAL') === false) {
|
||||
$v['length'] = isset($v['sub_part']) ? (int) $v['sub_part'] : null;
|
||||
}
|
||||
|
||||
$tableIndexes[$k] = $v;
|
||||
}
|
||||
|
||||
return parent::_getPortableTableIndexesList($tableIndexes, $tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableDatabaseDefinition($database)
|
||||
{
|
||||
return $database['Database'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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 = $origType = $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 = AbstractMySQLPlatform::LENGTH_LIMIT_TINYTEXT;
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_TEXT;
|
||||
break;
|
||||
|
||||
case 'mediumtext':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_MEDIUMTEXT;
|
||||
break;
|
||||
|
||||
case 'tinyblob':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_TINYBLOB;
|
||||
break;
|
||||
|
||||
case 'blob':
|
||||
$length = AbstractMySQLPlatform::LENGTH_LIMIT_BLOB;
|
||||
break;
|
||||
|
||||
case 'mediumblob':
|
||||
$length = AbstractMySQLPlatform::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']);
|
||||
}
|
||||
|
||||
if (isset($tableColumn['declarationMismatch'])) {
|
||||
$column->setPlatformOption('declarationMismatch', $tableColumn['declarationMismatch']);
|
||||
}
|
||||
|
||||
// Check underlying database type where doctrine type is inferred from DC2Type comment
|
||||
// and set a flag if it is not as expected.
|
||||
if ($type === 'json' && $origType !== $type && $this->expectedDbType($type, $options) !== $dbType) {
|
||||
$column->setPlatformOption('declarationMismatch', true);
|
||||
}
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the database data type for a given doctrine type and column
|
||||
*
|
||||
* Note that for data types that depend on length where length is not part of the column definition
|
||||
* and therefore the $tableColumn['length'] will not be set, for example TEXT (which could be LONGTEXT,
|
||||
* MEDIUMTEXT) or BLOB (LONGBLOB or TINYBLOB), the expectedDbType cannot be inferred exactly, merely
|
||||
* the default type.
|
||||
*
|
||||
* This method is intended to be used to determine underlying database type where doctrine type is
|
||||
* inferred from a DC2Type comment.
|
||||
*
|
||||
* @param mixed[] $tableColumn
|
||||
*/
|
||||
private function expectedDbType(string $type, array $tableColumn): string
|
||||
{
|
||||
$_type = Type::getType($type);
|
||||
$expectedDbType = strtolower($_type->getSQLDeclaration($tableColumn, $this->_platform));
|
||||
$expectedDbType = strtok($expectedDbType, '(), ');
|
||||
|
||||
return $expectedDbType === false ? '' : $expectedDbType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return Doctrine/Mysql-compatible column default values for MariaDB 10.2.7+ servers.
|
||||
*
|
||||
* - Since MariaDb 10.2.7 column defaults stored in information_schema are now quoted
|
||||
* to distinguish them from expressions (see MDEV-10134).
|
||||
* - CURRENT_TIMESTAMP, CURRENT_TIME, CURRENT_DATE are stored in information_schema
|
||||
* as current_timestamp(), currdate(), currtime()
|
||||
* - Quoted 'NULL' is not enforced by Maria, it is technically possible to have
|
||||
* null in some circumstances (see https://jira.mariadb.org/browse/MDEV-14053)
|
||||
* - \' is always stored as '' in information_schema (normalized)
|
||||
*
|
||||
* @link https://mariadb.com/kb/en/library/information-schema-columns-table/
|
||||
* @link https://jira.mariadb.org/browse/MDEV-13132
|
||||
*
|
||||
* @param string|null $columnDefault default value as stored in information_schema for MariaDB >= 10.2.7
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeysList($tableForeignKeys)
|
||||
{
|
||||
$list = [];
|
||||
foreach ($tableForeignKeys as $value) {
|
||||
$value = array_change_key_case($value, CASE_LOWER);
|
||||
if (! isset($list[$value['constraint_name']])) {
|
||||
if (! isset($value['delete_rule']) || $value['delete_rule'] === 'RESTRICT') {
|
||||
$value['delete_rule'] = null;
|
||||
}
|
||||
|
||||
if (! isset($value['update_rule']) || $value['update_rule'] === 'RESTRICT') {
|
||||
$value['update_rule'] = null;
|
||||
}
|
||||
|
||||
$list[$value['constraint_name']] = [
|
||||
'name' => $value['constraint_name'],
|
||||
'local' => [],
|
||||
'foreign' => [],
|
||||
'foreignTable' => $value['referenced_table_name'],
|
||||
'onDelete' => $value['delete_rule'],
|
||||
'onUpdate' => $value['update_rule'],
|
||||
];
|
||||
}
|
||||
|
||||
$list[$value['constraint_name']]['local'][] = $value['column_name'];
|
||||
$list[$value['constraint_name']]['foreign'][] = $value['referenced_column_name'];
|
||||
}
|
||||
|
||||
return parent::_getPortableTableForeignKeysList($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeyDefinition($tableForeignKey): ForeignKeyConstraint
|
||||
{
|
||||
return new ForeignKeyConstraint(
|
||||
$tableForeignKey['local'],
|
||||
$tableForeignKey['foreignTable'],
|
||||
$tableForeignKey['foreign'],
|
||||
$tableForeignKey['name'],
|
||||
[
|
||||
'onDelete' => $tableForeignKey['onDelete'],
|
||||
'onUpdate' => $tableForeignKey['onUpdate'],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function createComparator(): Comparator
|
||||
{
|
||||
return new MySQL\Comparator(
|
||||
$this->_platform,
|
||||
new CachingCollationMetadataProvider(
|
||||
new ConnectionCollationMetadataProvider($this->_conn),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
protected function selectTableNames(string $databaseName): Result
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT TABLE_NAME
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA = ?
|
||||
AND TABLE_TYPE = 'BASE TABLE'
|
||||
ORDER BY TABLE_NAME
|
||||
SQL;
|
||||
|
||||
return $this->_conn->executeQuery($sql, [$databaseName]);
|
||||
}
|
||||
|
||||
protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
// @todo 4.0 - call getColumnTypeSQLSnippet() instead
|
||||
[$columnTypeSQL, $joinCheckConstraintSQL] = $this->_platform->getColumnTypeSQLSnippets('c', $databaseName);
|
||||
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' c.TABLE_NAME,';
|
||||
}
|
||||
|
||||
$sql .= <<<SQL
|
||||
c.COLUMN_NAME AS field,
|
||||
$columnTypeSQL AS type,
|
||||
c.IS_NULLABLE AS `null`,
|
||||
c.COLUMN_KEY AS `key`,
|
||||
c.COLUMN_DEFAULT AS `default`,
|
||||
c.EXTRA,
|
||||
c.COLUMN_COMMENT AS comment,
|
||||
c.CHARACTER_SET_NAME AS characterset,
|
||||
c.COLLATION_NAME AS collation
|
||||
FROM information_schema.COLUMNS c
|
||||
INNER JOIN information_schema.TABLES t
|
||||
ON t.TABLE_NAME = c.TABLE_NAME
|
||||
$joinCheckConstraintSQL
|
||||
SQL;
|
||||
|
||||
// The schema name is passed multiple times as a literal in the WHERE clause instead of using a JOIN condition
|
||||
// in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions
|
||||
// caused by https://bugs.mysql.com/bug.php?id=81347
|
||||
$conditions = ['c.TABLE_SCHEMA = ?', 't.TABLE_SCHEMA = ?', "t.TABLE_TYPE = 'BASE TABLE'"];
|
||||
$params = [$databaseName, $databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 't.TABLE_NAME = ?';
|
||||
$params[] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY ORDINAL_POSITION';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' TABLE_NAME,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
NON_UNIQUE AS Non_Unique,
|
||||
INDEX_NAME AS Key_name,
|
||||
COLUMN_NAME AS Column_Name,
|
||||
SUB_PART AS Sub_Part,
|
||||
INDEX_TYPE AS Index_Type
|
||||
FROM information_schema.STATISTICS
|
||||
SQL;
|
||||
|
||||
$conditions = ['TABLE_SCHEMA = ?'];
|
||||
$params = [$databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'TABLE_NAME = ?';
|
||||
$params[] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY SEQ_IN_INDEX';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT DISTINCT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' k.TABLE_NAME,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
k.CONSTRAINT_NAME,
|
||||
k.COLUMN_NAME,
|
||||
k.REFERENCED_TABLE_NAME,
|
||||
k.REFERENCED_COLUMN_NAME,
|
||||
k.ORDINAL_POSITION /*!50116,
|
||||
c.UPDATE_RULE,
|
||||
c.DELETE_RULE */
|
||||
FROM information_schema.key_column_usage k /*!50116
|
||||
INNER JOIN information_schema.referential_constraints c
|
||||
ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME
|
||||
AND c.TABLE_NAME = k.TABLE_NAME */
|
||||
SQL;
|
||||
|
||||
$conditions = ['k.TABLE_SCHEMA = ?'];
|
||||
$params = [$databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'k.TABLE_NAME = ?';
|
||||
$params[] = $tableName;
|
||||
}
|
||||
|
||||
$conditions[] = 'k.REFERENCED_COLUMN_NAME IS NOT NULL';
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions)
|
||||
// The schema name is passed multiple times in the WHERE clause instead of using a JOIN condition
|
||||
// in order to avoid performance issues on MySQL older than 8.0 and the corresponding MariaDB versions
|
||||
// caused by https://bugs.mysql.com/bug.php?id=81347.
|
||||
// Use a string literal for the database name since the internal PDO SQL parser
|
||||
// cannot recognize parameter placeholders inside conditional comments
|
||||
. ' /*!50116 AND c.CONSTRAINT_SCHEMA = ' . $this->_conn->quote($databaseName) . ' */'
|
||||
. ' ORDER BY k.ORDINAL_POSITION';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT t.TABLE_NAME,
|
||||
t.ENGINE,
|
||||
t.AUTO_INCREMENT,
|
||||
t.TABLE_COMMENT,
|
||||
t.CREATE_OPTIONS,
|
||||
t.TABLE_COLLATION,
|
||||
ccsa.CHARACTER_SET_NAME
|
||||
FROM information_schema.TABLES t
|
||||
INNER JOIN information_schema.COLLATION_CHARACTER_SET_APPLICABILITY ccsa
|
||||
ON ccsa.COLLATION_NAME = t.TABLE_COLLATION
|
||||
SQL;
|
||||
|
||||
$conditions = ['t.TABLE_SCHEMA = ?'];
|
||||
$params = [$databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 't.TABLE_NAME = ?';
|
||||
$params[] = $tableName;
|
||||
}
|
||||
|
||||
$conditions[] = "t.TABLE_TYPE = 'BASE TABLE'";
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions);
|
||||
|
||||
/** @var array<string,array<string,mixed>> $metadata */
|
||||
$metadata = $this->_conn->executeQuery($sql, $params)
|
||||
->fetchAllAssociativeIndexed();
|
||||
|
||||
$tableOptions = [];
|
||||
foreach ($metadata as $table => $data) {
|
||||
$data = array_change_key_case($data, CASE_LOWER);
|
||||
|
||||
$tableOptions[$table] = [
|
||||
'engine' => $data['engine'],
|
||||
'collation' => $data['table_collation'],
|
||||
'charset' => $data['character_set_name'],
|
||||
'autoincrement' => $data['auto_increment'],
|
||||
'comment' => $data['table_comment'],
|
||||
'create_options' => $this->parseCreateOptions($data['create_options']),
|
||||
];
|
||||
}
|
||||
|
||||
return $tableOptions;
|
||||
}
|
||||
|
||||
/** @return string[]|true[] */
|
||||
private function parseCreateOptions(?string $string): array
|
||||
{
|
||||
$options = [];
|
||||
|
||||
if ($string === null || $string === '') {
|
||||
return $options;
|
||||
}
|
||||
|
||||
foreach (explode(' ', $string) as $pair) {
|
||||
$parts = explode('=', $pair, 2);
|
||||
|
||||
$options[$parts[0]] = $parts[1] ?? true;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,539 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\OraclePlatform;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_change_key_case;
|
||||
use function array_values;
|
||||
use function implode;
|
||||
use function is_string;
|
||||
use function preg_match;
|
||||
use function str_replace;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function strtoupper;
|
||||
use function trim;
|
||||
|
||||
use const CASE_LOWER;
|
||||
|
||||
/**
|
||||
* Oracle Schema Manager.
|
||||
*
|
||||
* @extends AbstractSchemaManager<OraclePlatform>
|
||||
*/
|
||||
class OracleSchemaManager extends AbstractSchemaManager
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableNames()
|
||||
{
|
||||
return $this->doListTableNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTables()
|
||||
{
|
||||
return $this->doListTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see introspectTable()} instead.
|
||||
*/
|
||||
public function listTableDetails($name)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5595',
|
||||
'%s is deprecated. Use introspectTable() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return $this->doListTableDetails($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableColumns($table, $database = null)
|
||||
{
|
||||
return $this->doListTableColumns($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableIndexes($table)
|
||||
{
|
||||
return $this->doListTableIndexes($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableForeignKeys($table, $database = null)
|
||||
{
|
||||
return $this->doListTableForeignKeys($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableViewDefinition($view)
|
||||
{
|
||||
$view = array_change_key_case($view, CASE_LOWER);
|
||||
|
||||
return new View($this->getQuotedIdentifierName($view['view_name']), $view['text']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableDefinition($table)
|
||||
{
|
||||
$table = array_change_key_case($table, CASE_LOWER);
|
||||
|
||||
return $this->getQuotedIdentifierName($table['table_name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
|
||||
*/
|
||||
protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
|
||||
{
|
||||
$indexBuffer = [];
|
||||
foreach ($tableIndexes as $tableIndex) {
|
||||
$tableIndex = array_change_key_case($tableIndex, CASE_LOWER);
|
||||
|
||||
$keyName = strtolower($tableIndex['name']);
|
||||
$buffer = [];
|
||||
|
||||
if ($tableIndex['is_primary'] === 'P') {
|
||||
$keyName = 'primary';
|
||||
$buffer['primary'] = true;
|
||||
$buffer['non_unique'] = false;
|
||||
} else {
|
||||
$buffer['primary'] = false;
|
||||
$buffer['non_unique'] = ! $tableIndex['is_unique'];
|
||||
}
|
||||
|
||||
$buffer['key_name'] = $keyName;
|
||||
$buffer['column_name'] = $this->getQuotedIdentifierName($tableIndex['column_name']);
|
||||
$indexBuffer[] = $buffer;
|
||||
}
|
||||
|
||||
return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableColumnDefinition($tableColumn)
|
||||
{
|
||||
$tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
|
||||
|
||||
$dbType = strtolower($tableColumn['data_type']);
|
||||
if (strpos($dbType, 'timestamp(') === 0) {
|
||||
if (strpos($dbType, 'with time zone') !== false) {
|
||||
$dbType = 'timestamptz';
|
||||
} else {
|
||||
$dbType = 'timestamp';
|
||||
}
|
||||
}
|
||||
|
||||
$unsigned = $fixed = $precision = $scale = $length = null;
|
||||
|
||||
if (! isset($tableColumn['column_name'])) {
|
||||
$tableColumn['column_name'] = '';
|
||||
}
|
||||
|
||||
// Default values returned from database sometimes have trailing spaces.
|
||||
if (is_string($tableColumn['data_default'])) {
|
||||
$tableColumn['data_default'] = trim($tableColumn['data_default']);
|
||||
}
|
||||
|
||||
if ($tableColumn['data_default'] === '' || $tableColumn['data_default'] === 'NULL') {
|
||||
$tableColumn['data_default'] = null;
|
||||
}
|
||||
|
||||
if ($tableColumn['data_default'] !== null) {
|
||||
// Default values returned from database are represented as literal expressions
|
||||
if (preg_match('/^\'(.*)\'$/s', $tableColumn['data_default'], $matches) === 1) {
|
||||
$tableColumn['data_default'] = str_replace("''", "'", $matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($tableColumn['data_precision'] !== null) {
|
||||
$precision = (int) $tableColumn['data_precision'];
|
||||
}
|
||||
|
||||
if ($tableColumn['data_scale'] !== null) {
|
||||
$scale = (int) $tableColumn['data_scale'];
|
||||
}
|
||||
|
||||
$type = $this->_platform->getDoctrineTypeMapping($dbType);
|
||||
$type = $this->extractDoctrineTypeFromComment($tableColumn['comments'], $type);
|
||||
$tableColumn['comments'] = $this->removeDoctrineTypeFromComment($tableColumn['comments'], $type);
|
||||
|
||||
switch ($dbType) {
|
||||
case 'number':
|
||||
if ($precision === 20 && $scale === 0) {
|
||||
$type = 'bigint';
|
||||
} elseif ($precision === 5 && $scale === 0) {
|
||||
$type = 'smallint';
|
||||
} elseif ($precision === 1 && $scale === 0) {
|
||||
$type = 'boolean';
|
||||
} elseif ($scale > 0) {
|
||||
$type = 'decimal';
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'varchar':
|
||||
case 'varchar2':
|
||||
case 'nvarchar2':
|
||||
$length = $tableColumn['char_length'];
|
||||
$fixed = false;
|
||||
break;
|
||||
|
||||
case 'raw':
|
||||
$length = $tableColumn['data_length'];
|
||||
$fixed = true;
|
||||
break;
|
||||
|
||||
case 'char':
|
||||
case 'nchar':
|
||||
$length = $tableColumn['char_length'];
|
||||
$fixed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$options = [
|
||||
'notnull' => $tableColumn['nullable'] === 'N',
|
||||
'fixed' => (bool) $fixed,
|
||||
'unsigned' => (bool) $unsigned,
|
||||
'default' => $tableColumn['data_default'],
|
||||
'length' => $length,
|
||||
'precision' => $precision,
|
||||
'scale' => $scale,
|
||||
'comment' => isset($tableColumn['comments']) && $tableColumn['comments'] !== ''
|
||||
? $tableColumn['comments']
|
||||
: null,
|
||||
];
|
||||
|
||||
return new Column($this->getQuotedIdentifierName($tableColumn['column_name']), Type::getType($type), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeysList($tableForeignKeys)
|
||||
{
|
||||
$list = [];
|
||||
foreach ($tableForeignKeys as $value) {
|
||||
$value = array_change_key_case($value, CASE_LOWER);
|
||||
if (! isset($list[$value['constraint_name']])) {
|
||||
if ($value['delete_rule'] === 'NO ACTION') {
|
||||
$value['delete_rule'] = null;
|
||||
}
|
||||
|
||||
$list[$value['constraint_name']] = [
|
||||
'name' => $this->getQuotedIdentifierName($value['constraint_name']),
|
||||
'local' => [],
|
||||
'foreign' => [],
|
||||
'foreignTable' => $value['references_table'],
|
||||
'onDelete' => $value['delete_rule'],
|
||||
];
|
||||
}
|
||||
|
||||
$localColumn = $this->getQuotedIdentifierName($value['local_column']);
|
||||
$foreignColumn = $this->getQuotedIdentifierName($value['foreign_column']);
|
||||
|
||||
$list[$value['constraint_name']]['local'][$value['position']] = $localColumn;
|
||||
$list[$value['constraint_name']]['foreign'][$value['position']] = $foreignColumn;
|
||||
}
|
||||
|
||||
return parent::_getPortableTableForeignKeysList($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeyDefinition($tableForeignKey): ForeignKeyConstraint
|
||||
{
|
||||
return new ForeignKeyConstraint(
|
||||
array_values($tableForeignKey['local']),
|
||||
$this->getQuotedIdentifierName($tableForeignKey['foreignTable']),
|
||||
array_values($tableForeignKey['foreign']),
|
||||
$this->getQuotedIdentifierName($tableForeignKey['name']),
|
||||
['onDelete' => $tableForeignKey['onDelete']],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableSequenceDefinition($sequence)
|
||||
{
|
||||
$sequence = array_change_key_case($sequence, CASE_LOWER);
|
||||
|
||||
return new Sequence(
|
||||
$this->getQuotedIdentifierName($sequence['sequence_name']),
|
||||
(int) $sequence['increment_by'],
|
||||
(int) $sequence['min_value'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableDatabaseDefinition($database)
|
||||
{
|
||||
$database = array_change_key_case($database, CASE_LOWER);
|
||||
|
||||
return $database['username'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function createDatabase($database)
|
||||
{
|
||||
$statement = $this->_platform->getCreateDatabaseSQL($database);
|
||||
|
||||
$params = $this->_conn->getParams();
|
||||
|
||||
if (isset($params['password'])) {
|
||||
$statement .= ' IDENTIFIED BY ' . $params['password'];
|
||||
}
|
||||
|
||||
$this->_conn->executeStatement($statement);
|
||||
|
||||
$statement = 'GRANT DBA TO ' . $database;
|
||||
$this->_conn->executeStatement($statement);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal The method should be only used from within the OracleSchemaManager class hierarchy.
|
||||
*
|
||||
* @param string $table
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function dropAutoincrement($table)
|
||||
{
|
||||
$sql = $this->_platform->getDropAutoincrementSql($table);
|
||||
foreach ($sql as $query) {
|
||||
$this->_conn->executeStatement($query);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function dropTable($name)
|
||||
{
|
||||
$this->tryMethod('dropAutoincrement', $name);
|
||||
|
||||
parent::dropTable($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the quoted representation of the given identifier name.
|
||||
*
|
||||
* Quotes non-uppercase identifiers explicitly to preserve case
|
||||
* and thus make references to the particular identifier work.
|
||||
*
|
||||
* @param string $identifier The identifier to quote.
|
||||
*/
|
||||
private function getQuotedIdentifierName($identifier): string
|
||||
{
|
||||
if (preg_match('/[a-z]/', $identifier) === 1) {
|
||||
return $this->_platform->quoteIdentifier($identifier);
|
||||
}
|
||||
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
protected function selectTableNames(string $databaseName): Result
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT TABLE_NAME
|
||||
FROM ALL_TABLES
|
||||
WHERE OWNER = :OWNER
|
||||
ORDER BY TABLE_NAME
|
||||
SQL;
|
||||
|
||||
return $this->_conn->executeQuery($sql, ['OWNER' => $databaseName]);
|
||||
}
|
||||
|
||||
protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' C.TABLE_NAME,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
C.COLUMN_NAME,
|
||||
C.DATA_TYPE,
|
||||
C.DATA_DEFAULT,
|
||||
C.DATA_PRECISION,
|
||||
C.DATA_SCALE,
|
||||
C.CHAR_LENGTH,
|
||||
C.DATA_LENGTH,
|
||||
C.NULLABLE,
|
||||
D.COMMENTS
|
||||
FROM ALL_TAB_COLUMNS C
|
||||
INNER JOIN ALL_TABLES T
|
||||
ON T.OWNER = C.OWNER
|
||||
AND T.TABLE_NAME = C.TABLE_NAME
|
||||
LEFT JOIN ALL_COL_COMMENTS D
|
||||
ON D.OWNER = C.OWNER
|
||||
AND D.TABLE_NAME = C.TABLE_NAME
|
||||
AND D.COLUMN_NAME = C.COLUMN_NAME
|
||||
SQL;
|
||||
|
||||
$conditions = ['C.OWNER = :OWNER'];
|
||||
$params = ['OWNER' => $databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'C.TABLE_NAME = :TABLE_NAME';
|
||||
$params['TABLE_NAME'] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY C.COLUMN_ID';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' IND_COL.TABLE_NAME,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
IND_COL.INDEX_NAME AS NAME,
|
||||
IND.INDEX_TYPE AS TYPE,
|
||||
DECODE(IND.UNIQUENESS, 'NONUNIQUE', 0, 'UNIQUE', 1) AS IS_UNIQUE,
|
||||
IND_COL.COLUMN_NAME,
|
||||
IND_COL.COLUMN_POSITION AS COLUMN_POS,
|
||||
CON.CONSTRAINT_TYPE AS IS_PRIMARY
|
||||
FROM ALL_IND_COLUMNS IND_COL
|
||||
LEFT JOIN ALL_INDEXES IND
|
||||
ON IND.OWNER = IND_COL.INDEX_OWNER
|
||||
AND IND.INDEX_NAME = IND_COL.INDEX_NAME
|
||||
LEFT JOIN ALL_CONSTRAINTS CON
|
||||
ON CON.OWNER = IND_COL.INDEX_OWNER
|
||||
AND CON.INDEX_NAME = IND_COL.INDEX_NAME
|
||||
SQL;
|
||||
|
||||
$conditions = ['IND_COL.INDEX_OWNER = :OWNER'];
|
||||
$params = ['OWNER' => $databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'IND_COL.TABLE_NAME = :TABLE_NAME';
|
||||
$params['TABLE_NAME'] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY IND_COL.TABLE_NAME, IND_COL.INDEX_NAME'
|
||||
. ', IND_COL.COLUMN_POSITION';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' COLS.TABLE_NAME,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
ALC.CONSTRAINT_NAME,
|
||||
ALC.DELETE_RULE,
|
||||
COLS.COLUMN_NAME LOCAL_COLUMN,
|
||||
COLS.POSITION,
|
||||
R_COLS.TABLE_NAME REFERENCES_TABLE,
|
||||
R_COLS.COLUMN_NAME FOREIGN_COLUMN
|
||||
FROM ALL_CONS_COLUMNS COLS
|
||||
LEFT JOIN ALL_CONSTRAINTS ALC ON ALC.OWNER = COLS.OWNER AND ALC.CONSTRAINT_NAME = COLS.CONSTRAINT_NAME
|
||||
LEFT JOIN ALL_CONS_COLUMNS R_COLS ON R_COLS.OWNER = ALC.R_OWNER AND
|
||||
R_COLS.CONSTRAINT_NAME = ALC.R_CONSTRAINT_NAME AND
|
||||
R_COLS.POSITION = COLS.POSITION
|
||||
SQL;
|
||||
|
||||
$conditions = ["ALC.CONSTRAINT_TYPE = 'R'", 'COLS.OWNER = :OWNER'];
|
||||
$params = ['OWNER' => $databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'COLS.TABLE_NAME = :TABLE_NAME';
|
||||
$params['TABLE_NAME'] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY COLS.TABLE_NAME, COLS.CONSTRAINT_NAME'
|
||||
. ', COLS.POSITION';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
|
||||
{
|
||||
$sql = 'SELECT TABLE_NAME, COMMENTS';
|
||||
|
||||
$conditions = ['OWNER = :OWNER'];
|
||||
$params = ['OWNER' => $databaseName];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 'TABLE_NAME = :TABLE_NAME';
|
||||
$params['TABLE_NAME'] = $tableName;
|
||||
}
|
||||
|
||||
$sql .= ' FROM ALL_TAB_COMMENTS WHERE ' . implode(' AND ', $conditions);
|
||||
|
||||
/** @var array<string,array<string,mixed>> $metadata */
|
||||
$metadata = $this->_conn->executeQuery($sql, $params)
|
||||
->fetchAllAssociativeIndexed();
|
||||
|
||||
$tableOptions = [];
|
||||
foreach ($metadata as $table => $data) {
|
||||
$data = array_change_key_case($data, CASE_LOWER);
|
||||
|
||||
$tableOptions[$table] = [
|
||||
'comment' => $data['comments'],
|
||||
];
|
||||
}
|
||||
|
||||
return $tableOptions;
|
||||
}
|
||||
|
||||
protected function normalizeName(string $name): string
|
||||
{
|
||||
$identifier = new Identifier($name);
|
||||
|
||||
return $identifier->isQuoted() ? $identifier->getName() : strtoupper($name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,774 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\DBAL\Types\JsonType;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_change_key_case;
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_shift;
|
||||
use function assert;
|
||||
use function explode;
|
||||
use function get_class;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function trim;
|
||||
|
||||
use const CASE_LOWER;
|
||||
|
||||
/**
|
||||
* PostgreSQL Schema Manager.
|
||||
*
|
||||
* @extends AbstractSchemaManager<PostgreSQLPlatform>
|
||||
*/
|
||||
class PostgreSQLSchemaManager extends AbstractSchemaManager
|
||||
{
|
||||
/** @var string[]|null */
|
||||
private ?array $existingSchemaPaths = null;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableNames()
|
||||
{
|
||||
return $this->doListTableNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTables()
|
||||
{
|
||||
return $this->doListTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see introspectTable()} instead.
|
||||
*/
|
||||
public function listTableDetails($name)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5595',
|
||||
'%s is deprecated. Use introspectTable() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return $this->doListTableDetails($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableColumns($table, $database = null)
|
||||
{
|
||||
return $this->doListTableColumns($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableIndexes($table)
|
||||
{
|
||||
return $this->doListTableIndexes($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableForeignKeys($table, $database = null)
|
||||
{
|
||||
return $this->doListTableForeignKeys($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the existing schema names.
|
||||
*
|
||||
* @deprecated Use {@see listSchemaNames()} instead.
|
||||
*
|
||||
* @return string[]
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getSchemaNames()
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/issues/4503',
|
||||
'PostgreSQLSchemaManager::getSchemaNames() is deprecated,'
|
||||
. ' use PostgreSQLSchemaManager::listSchemaNames() instead.',
|
||||
);
|
||||
|
||||
return $this->listNamespaceNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listSchemaNames(): array
|
||||
{
|
||||
return $this->_conn->fetchFirstColumn(
|
||||
<<<'SQL'
|
||||
SELECT schema_name
|
||||
FROM information_schema.schemata
|
||||
WHERE schema_name NOT LIKE 'pg\_%'
|
||||
AND schema_name != 'information_schema'
|
||||
SQL,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public function getSchemaSearchPaths()
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4821',
|
||||
'PostgreSQLSchemaManager::getSchemaSearchPaths() is deprecated.',
|
||||
);
|
||||
|
||||
$params = $this->_conn->getParams();
|
||||
|
||||
$searchPaths = $this->_conn->fetchOne('SHOW search_path');
|
||||
assert($searchPaths !== false);
|
||||
|
||||
$schema = explode(',', $searchPaths);
|
||||
|
||||
if (isset($params['user'])) {
|
||||
$schema = str_replace('"$user"', $params['user'], $schema);
|
||||
}
|
||||
|
||||
return array_map('trim', $schema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets names of all existing schemas in the current users search path.
|
||||
*
|
||||
* This is a PostgreSQL only function.
|
||||
*
|
||||
* @internal The method should be only used from within the PostgreSQLSchemaManager class hierarchy.
|
||||
*
|
||||
* @return string[]
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getExistingSchemaSearchPaths()
|
||||
{
|
||||
if ($this->existingSchemaPaths === null) {
|
||||
$this->determineExistingSchemaSearchPaths();
|
||||
}
|
||||
|
||||
assert($this->existingSchemaPaths !== null);
|
||||
|
||||
return $this->existingSchemaPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the current schema.
|
||||
*
|
||||
* @return string|null
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function getCurrentSchema()
|
||||
{
|
||||
$schemas = $this->getExistingSchemaSearchPaths();
|
||||
|
||||
return array_shift($schemas);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets or resets the order of the existing schemas in the current search path of the user.
|
||||
*
|
||||
* This is a PostgreSQL only function.
|
||||
*
|
||||
* @internal The method should be only used from within the PostgreSQLSchemaManager class hierarchy.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function determineExistingSchemaSearchPaths()
|
||||
{
|
||||
$names = $this->listSchemaNames();
|
||||
$paths = $this->getSchemaSearchPaths();
|
||||
|
||||
$this->existingSchemaPaths = array_filter($paths, static function ($v) use ($names): bool {
|
||||
return in_array($v, $names, true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
|
||||
{
|
||||
$onUpdate = null;
|
||||
$onDelete = null;
|
||||
|
||||
if (
|
||||
preg_match(
|
||||
'(ON UPDATE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))',
|
||||
$tableForeignKey['condef'],
|
||||
$match,
|
||||
) === 1
|
||||
) {
|
||||
$onUpdate = $match[1];
|
||||
}
|
||||
|
||||
if (
|
||||
preg_match(
|
||||
'(ON DELETE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))',
|
||||
$tableForeignKey['condef'],
|
||||
$match,
|
||||
) === 1
|
||||
) {
|
||||
$onDelete = $match[1];
|
||||
}
|
||||
|
||||
$result = preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values);
|
||||
assert($result === 1);
|
||||
|
||||
// PostgreSQL returns identifiers that are keywords with quotes, we need them later, don't get
|
||||
// the idea to trim them here.
|
||||
$localColumns = array_map('trim', explode(',', $values[1]));
|
||||
$foreignColumns = array_map('trim', explode(',', $values[3]));
|
||||
$foreignTable = $values[2];
|
||||
|
||||
return new ForeignKeyConstraint(
|
||||
$localColumns,
|
||||
$foreignTable,
|
||||
$foreignColumns,
|
||||
$tableForeignKey['conname'],
|
||||
['onUpdate' => $onUpdate, 'onDelete' => $onDelete],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableViewDefinition($view)
|
||||
{
|
||||
return new View($view['schemaname'] . '.' . $view['viewname'], $view['definition']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableDefinition($table)
|
||||
{
|
||||
$currentSchema = $this->getCurrentSchema();
|
||||
|
||||
if ($table['schema_name'] === $currentSchema) {
|
||||
return $table['table_name'];
|
||||
}
|
||||
|
||||
return $table['schema_name'] . '.' . $table['table_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
|
||||
*/
|
||||
protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
|
||||
{
|
||||
$buffer = [];
|
||||
foreach ($tableIndexes as $row) {
|
||||
$colNumbers = array_map('intval', explode(' ', $row['indkey']));
|
||||
$columnNameSql = sprintf(
|
||||
'SELECT attnum, attname FROM pg_attribute WHERE attrelid=%d AND attnum IN (%s) ORDER BY attnum ASC',
|
||||
$row['indrelid'],
|
||||
implode(' ,', $colNumbers),
|
||||
);
|
||||
|
||||
$indexColumns = $this->_conn->fetchAllAssociative($columnNameSql);
|
||||
|
||||
// required for getting the order of the columns right.
|
||||
foreach ($colNumbers as $colNum) {
|
||||
foreach ($indexColumns as $colRow) {
|
||||
if ($colNum !== $colRow['attnum']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$buffer[] = [
|
||||
'key_name' => $row['relname'],
|
||||
'column_name' => trim($colRow['attname']),
|
||||
'non_unique' => ! $row['indisunique'],
|
||||
'primary' => $row['indisprimary'],
|
||||
'where' => $row['where'],
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return parent::_getPortableTableIndexesList($buffer, $tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableDatabaseDefinition($database)
|
||||
{
|
||||
return $database['datname'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see listSchemaNames()} instead.
|
||||
*/
|
||||
protected function getPortableNamespaceDefinition(array $namespace)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/issues/4503',
|
||||
'PostgreSQLSchemaManager::getPortableNamespaceDefinition() is deprecated,'
|
||||
. ' use PostgreSQLSchemaManager::listSchemaNames() instead.',
|
||||
);
|
||||
|
||||
return $namespace['nspname'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableSequenceDefinition($sequence)
|
||||
{
|
||||
if ($sequence['schemaname'] !== 'public') {
|
||||
$sequenceName = $sequence['schemaname'] . '.' . $sequence['relname'];
|
||||
} else {
|
||||
$sequenceName = $sequence['relname'];
|
||||
}
|
||||
|
||||
return new Sequence($sequenceName, (int) $sequence['increment_by'], (int) $sequence['min_value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableColumnDefinition($tableColumn)
|
||||
{
|
||||
$tableColumn = array_change_key_case($tableColumn, CASE_LOWER);
|
||||
|
||||
if (strtolower($tableColumn['type']) === 'varchar' || strtolower($tableColumn['type']) === 'bpchar') {
|
||||
// get length from varchar definition
|
||||
$length = preg_replace('~.*\(([0-9]*)\).*~', '$1', $tableColumn['complete_type']);
|
||||
$tableColumn['length'] = $length;
|
||||
}
|
||||
|
||||
$matches = [];
|
||||
|
||||
$autoincrement = false;
|
||||
|
||||
if (
|
||||
$tableColumn['default'] !== null
|
||||
&& preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches) === 1
|
||||
) {
|
||||
$tableColumn['sequence'] = $matches[1];
|
||||
$tableColumn['default'] = null;
|
||||
$autoincrement = true;
|
||||
}
|
||||
|
||||
if ($tableColumn['default'] !== null) {
|
||||
if (preg_match("/^['(](.*)[')]::/", $tableColumn['default'], $matches) === 1) {
|
||||
$tableColumn['default'] = $matches[1];
|
||||
} elseif (preg_match('/^NULL::/', $tableColumn['default']) === 1) {
|
||||
$tableColumn['default'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$length = $tableColumn['length'] ?? null;
|
||||
if ($length === '-1' && isset($tableColumn['atttypmod'])) {
|
||||
$length = $tableColumn['atttypmod'] - 4;
|
||||
}
|
||||
|
||||
if ((int) $length <= 0) {
|
||||
$length = null;
|
||||
}
|
||||
|
||||
$fixed = null;
|
||||
|
||||
if (! isset($tableColumn['name'])) {
|
||||
$tableColumn['name'] = '';
|
||||
}
|
||||
|
||||
$precision = null;
|
||||
$scale = null;
|
||||
$jsonb = null;
|
||||
|
||||
$dbType = strtolower($tableColumn['type']);
|
||||
if (
|
||||
$tableColumn['domain_type'] !== null
|
||||
&& $tableColumn['domain_type'] !== ''
|
||||
&& ! $this->_platform->hasDoctrineTypeMappingFor($tableColumn['type'])
|
||||
) {
|
||||
$dbType = strtolower($tableColumn['domain_type']);
|
||||
$tableColumn['complete_type'] = $tableColumn['domain_complete_type'];
|
||||
}
|
||||
|
||||
$type = $this->_platform->getDoctrineTypeMapping($dbType);
|
||||
$type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
$tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
|
||||
switch ($dbType) {
|
||||
case 'smallint':
|
||||
case 'int2':
|
||||
$tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
|
||||
$length = null;
|
||||
break;
|
||||
|
||||
case 'int':
|
||||
case 'int4':
|
||||
case 'integer':
|
||||
$tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
|
||||
$length = null;
|
||||
break;
|
||||
|
||||
case 'bigint':
|
||||
case 'int8':
|
||||
$tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
|
||||
$length = null;
|
||||
break;
|
||||
|
||||
case 'bool':
|
||||
case 'boolean':
|
||||
if ($tableColumn['default'] === 'true') {
|
||||
$tableColumn['default'] = true;
|
||||
}
|
||||
|
||||
if ($tableColumn['default'] === 'false') {
|
||||
$tableColumn['default'] = false;
|
||||
}
|
||||
|
||||
$length = null;
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
case '_varchar':
|
||||
case 'varchar':
|
||||
$tableColumn['default'] = $this->parseDefaultExpression($tableColumn['default']);
|
||||
$fixed = false;
|
||||
break;
|
||||
case 'interval':
|
||||
$fixed = false;
|
||||
break;
|
||||
|
||||
case 'char':
|
||||
case 'bpchar':
|
||||
$fixed = true;
|
||||
break;
|
||||
|
||||
case 'float':
|
||||
case 'float4':
|
||||
case 'float8':
|
||||
case 'double':
|
||||
case 'double precision':
|
||||
case 'real':
|
||||
case 'decimal':
|
||||
case 'money':
|
||||
case 'numeric':
|
||||
$tableColumn['default'] = $this->fixVersion94NegativeNumericDefaultValue($tableColumn['default']);
|
||||
|
||||
if (
|
||||
preg_match(
|
||||
'([A-Za-z]+\(([0-9]+),([0-9]+)\))',
|
||||
$tableColumn['complete_type'],
|
||||
$match,
|
||||
) === 1
|
||||
) {
|
||||
$precision = $match[1];
|
||||
$scale = $match[2];
|
||||
$length = null;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'year':
|
||||
$length = null;
|
||||
break;
|
||||
|
||||
// PostgreSQL 9.4+ only
|
||||
case 'jsonb':
|
||||
$jsonb = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (
|
||||
$tableColumn['default'] !== null && preg_match(
|
||||
"('([^']+)'::)",
|
||||
$tableColumn['default'],
|
||||
$match,
|
||||
) === 1
|
||||
) {
|
||||
$tableColumn['default'] = $match[1];
|
||||
}
|
||||
|
||||
$options = [
|
||||
'length' => $length,
|
||||
'notnull' => (bool) $tableColumn['isnotnull'],
|
||||
'default' => $tableColumn['default'],
|
||||
'precision' => $precision,
|
||||
'scale' => $scale,
|
||||
'fixed' => $fixed,
|
||||
'autoincrement' => $autoincrement,
|
||||
'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== ''
|
||||
? $tableColumn['comment']
|
||||
: null,
|
||||
];
|
||||
|
||||
$column = new Column($tableColumn['field'], Type::getType($type), $options);
|
||||
|
||||
if (! empty($tableColumn['collation'])) {
|
||||
$column->setPlatformOption('collation', $tableColumn['collation']);
|
||||
}
|
||||
|
||||
if ($column->getType()->getName() === Types::JSON) {
|
||||
if (! $column->getType() instanceof JsonType) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5049',
|
||||
<<<'DEPRECATION'
|
||||
%s not extending %s while being named %s is deprecated,
|
||||
and will lead to jsonb never to being used in 4.0.,
|
||||
DEPRECATION,
|
||||
get_class($column->getType()),
|
||||
JsonType::class,
|
||||
Types::JSON,
|
||||
);
|
||||
}
|
||||
|
||||
$column->setPlatformOption('jsonb', $jsonb);
|
||||
}
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* PostgreSQL 9.4 puts parentheses around negative numeric default values that need to be stripped eventually.
|
||||
*
|
||||
* @param mixed $defaultValue
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function fixVersion94NegativeNumericDefaultValue($defaultValue)
|
||||
{
|
||||
if ($defaultValue !== null && strpos($defaultValue, '(') === 0) {
|
||||
return trim($defaultValue, '()');
|
||||
}
|
||||
|
||||
return $defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a default value expression as given by PostgreSQL
|
||||
*/
|
||||
private function parseDefaultExpression(?string $default): ?string
|
||||
{
|
||||
if ($default === null) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return str_replace("''", "'", $default);
|
||||
}
|
||||
|
||||
protected function selectTableNames(string $databaseName): Result
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT quote_ident(table_name) AS table_name,
|
||||
table_schema AS schema_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_catalog = ?
|
||||
AND table_schema NOT LIKE 'pg\_%'
|
||||
AND table_schema != 'information_schema'
|
||||
AND table_name != 'geometry_columns'
|
||||
AND table_name != 'spatial_ref_sys'
|
||||
AND table_type = 'BASE TABLE'
|
||||
SQL;
|
||||
|
||||
return $this->_conn->executeQuery($sql, [$databaseName]);
|
||||
}
|
||||
|
||||
protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' c.relname AS table_name, n.nspname AS schema_name,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
a.attnum,
|
||||
quote_ident(a.attname) AS field,
|
||||
t.typname AS type,
|
||||
format_type(a.atttypid, a.atttypmod) AS complete_type,
|
||||
(SELECT tc.collcollate FROM pg_catalog.pg_collation tc WHERE tc.oid = a.attcollation) AS collation,
|
||||
(SELECT t1.typname FROM pg_catalog.pg_type t1 WHERE t1.oid = t.typbasetype) AS domain_type,
|
||||
(SELECT format_type(t2.typbasetype, t2.typtypmod) FROM
|
||||
pg_catalog.pg_type t2 WHERE t2.typtype = 'd' AND t2.oid = a.atttypid) AS domain_complete_type,
|
||||
a.attnotnull AS isnotnull,
|
||||
(SELECT 't'
|
||||
FROM pg_index
|
||||
WHERE c.oid = pg_index.indrelid
|
||||
AND pg_index.indkey[0] = a.attnum
|
||||
AND pg_index.indisprimary = 't'
|
||||
) AS pri,
|
||||
(SELECT pg_get_expr(adbin, adrelid)
|
||||
FROM pg_attrdef
|
||||
WHERE c.oid = pg_attrdef.adrelid
|
||||
AND pg_attrdef.adnum=a.attnum
|
||||
) AS default,
|
||||
(SELECT pg_description.description
|
||||
FROM pg_description WHERE pg_description.objoid = c.oid AND a.attnum = pg_description.objsubid
|
||||
) AS comment
|
||||
FROM pg_attribute a
|
||||
INNER JOIN pg_class c
|
||||
ON c.oid = a.attrelid
|
||||
INNER JOIN pg_type t
|
||||
ON t.oid = a.atttypid
|
||||
INNER JOIN pg_namespace n
|
||||
ON n.oid = c.relnamespace
|
||||
LEFT JOIN pg_depend d
|
||||
ON d.objid = c.oid
|
||||
AND d.deptype = 'e'
|
||||
AND d.classid = (SELECT oid FROM pg_class WHERE relname = 'pg_class')
|
||||
SQL;
|
||||
|
||||
$conditions = array_merge([
|
||||
'a.attnum > 0',
|
||||
"c.relkind = 'r'",
|
||||
'd.refobjid IS NULL',
|
||||
], $this->buildQueryConditions($tableName));
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY a.attnum';
|
||||
|
||||
return $this->_conn->executeQuery($sql);
|
||||
}
|
||||
|
||||
protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
quote_ident(ic.relname) AS relname,
|
||||
i.indisunique,
|
||||
i.indisprimary,
|
||||
i.indkey,
|
||||
i.indrelid,
|
||||
pg_get_expr(indpred, indrelid) AS "where"
|
||||
FROM pg_index i
|
||||
JOIN pg_class AS tc ON tc.oid = i.indrelid
|
||||
JOIN pg_namespace tn ON tn.oid = tc.relnamespace
|
||||
JOIN pg_class AS ic ON ic.oid = i.indexrelid
|
||||
WHERE ic.oid IN (
|
||||
SELECT indexrelid
|
||||
FROM pg_index i, pg_class c, pg_namespace n
|
||||
SQL;
|
||||
|
||||
$conditions = array_merge([
|
||||
'c.oid = i.indrelid',
|
||||
'c.relnamespace = n.oid',
|
||||
], $this->buildQueryConditions($tableName));
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ')';
|
||||
|
||||
return $this->_conn->executeQuery($sql);
|
||||
}
|
||||
|
||||
protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' tc.relname AS table_name, tn.nspname AS schema_name,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
quote_ident(r.conname) as conname,
|
||||
pg_get_constraintdef(r.oid, true) as condef
|
||||
FROM pg_constraint r
|
||||
JOIN pg_class AS tc ON tc.oid = r.conrelid
|
||||
JOIN pg_namespace tn ON tn.oid = tc.relnamespace
|
||||
WHERE r.conrelid IN
|
||||
(
|
||||
SELECT c.oid
|
||||
FROM pg_class c, pg_namespace n
|
||||
SQL;
|
||||
|
||||
$conditions = array_merge(['n.oid = c.relnamespace'], $this->buildQueryConditions($tableName));
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ") AND r.contype = 'f'";
|
||||
|
||||
return $this->_conn->executeQuery($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT c.relname,
|
||||
CASE c.relpersistence WHEN 'u' THEN true ELSE false END as unlogged,
|
||||
obj_description(c.oid, 'pg_class') AS comment
|
||||
FROM pg_class c
|
||||
INNER JOIN pg_namespace n
|
||||
ON n.oid = c.relnamespace
|
||||
SQL;
|
||||
|
||||
$conditions = array_merge(["c.relkind = 'r'"], $this->buildQueryConditions($tableName));
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions);
|
||||
|
||||
return $this->_conn->fetchAllAssociativeIndexed($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $tableName
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
private function buildQueryConditions($tableName): array
|
||||
{
|
||||
$conditions = [];
|
||||
|
||||
if ($tableName !== null) {
|
||||
if (strpos($tableName, '.') !== false) {
|
||||
[$schemaName, $tableName] = explode('.', $tableName);
|
||||
$conditions[] = 'n.nspname = ' . $this->_platform->quoteStringLiteral($schemaName);
|
||||
} else {
|
||||
$conditions[] = 'n.nspname = ANY(current_schemas(false))';
|
||||
}
|
||||
|
||||
$identifier = new Identifier($tableName);
|
||||
$conditions[] = 'c.relname = ' . $this->_platform->quoteStringLiteral($identifier->getName());
|
||||
}
|
||||
|
||||
$conditions[] = "n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')";
|
||||
|
||||
return $conditions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,611 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\SQLServer;
|
||||
use Doctrine\DBAL\Platforms\SQLServerPlatform;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_change_key_case;
|
||||
use function assert;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function is_string;
|
||||
use function preg_match;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
use function strpos;
|
||||
use function strtok;
|
||||
|
||||
use const CASE_LOWER;
|
||||
|
||||
/**
|
||||
* SQL Server Schema Manager.
|
||||
*
|
||||
* @extends AbstractSchemaManager<SQLServerPlatform>
|
||||
*/
|
||||
class SQLServerSchemaManager extends AbstractSchemaManager
|
||||
{
|
||||
private ?string $databaseCollation = null;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableNames()
|
||||
{
|
||||
return $this->doListTableNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTables()
|
||||
{
|
||||
return $this->doListTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see introspectTable()} instead.
|
||||
*/
|
||||
public function listTableDetails($name)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5595',
|
||||
'%s is deprecated. Use introspectTable() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return $this->doListTableDetails($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableColumns($table, $database = null)
|
||||
{
|
||||
return $this->doListTableColumns($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableIndexes($table)
|
||||
{
|
||||
return $this->doListTableIndexes($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableForeignKeys($table, $database = null)
|
||||
{
|
||||
return $this->doListTableForeignKeys($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listSchemaNames(): array
|
||||
{
|
||||
return $this->_conn->fetchFirstColumn(
|
||||
<<<'SQL'
|
||||
SELECT name
|
||||
FROM sys.schemas
|
||||
WHERE name NOT IN('guest', 'INFORMATION_SCHEMA', 'sys')
|
||||
SQL,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableSequenceDefinition($sequence)
|
||||
{
|
||||
return new Sequence($sequence['name'], (int) $sequence['increment'], (int) $sequence['start_value']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableColumnDefinition($tableColumn)
|
||||
{
|
||||
$dbType = strtok($tableColumn['type'], '(), ');
|
||||
assert(is_string($dbType));
|
||||
|
||||
$fixed = null;
|
||||
$length = (int) $tableColumn['length'];
|
||||
$default = $tableColumn['default'];
|
||||
|
||||
if (! isset($tableColumn['name'])) {
|
||||
$tableColumn['name'] = '';
|
||||
}
|
||||
|
||||
if ($default !== null) {
|
||||
$default = $this->parseDefaultExpression($default);
|
||||
}
|
||||
|
||||
switch ($dbType) {
|
||||
case 'nchar':
|
||||
case 'ntext':
|
||||
// Unicode data requires 2 bytes per character
|
||||
$length /= 2;
|
||||
break;
|
||||
|
||||
case 'nvarchar':
|
||||
if ($length === -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Unicode data requires 2 bytes per character
|
||||
$length /= 2;
|
||||
break;
|
||||
|
||||
case 'varchar':
|
||||
// TEXT type is returned as VARCHAR(MAX) with a length of -1
|
||||
if ($length === -1) {
|
||||
$dbType = 'text';
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'varbinary':
|
||||
if ($length === -1) {
|
||||
$dbType = 'blob';
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if ($dbType === 'char' || $dbType === 'nchar' || $dbType === 'binary') {
|
||||
$fixed = true;
|
||||
}
|
||||
|
||||
$type = $this->_platform->getDoctrineTypeMapping($dbType);
|
||||
$type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
$tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
|
||||
|
||||
$options = [
|
||||
'unsigned' => false,
|
||||
'fixed' => (bool) $fixed,
|
||||
'default' => $default,
|
||||
'notnull' => (bool) $tableColumn['notnull'],
|
||||
'scale' => $tableColumn['scale'],
|
||||
'precision' => $tableColumn['precision'],
|
||||
'autoincrement' => (bool) $tableColumn['autoincrement'],
|
||||
'comment' => $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null,
|
||||
];
|
||||
|
||||
if ($length !== 0 && ($type === 'text' || $type === 'string' || $type === 'binary')) {
|
||||
$options['length'] = $length;
|
||||
}
|
||||
|
||||
$column = new Column($tableColumn['name'], Type::getType($type), $options);
|
||||
|
||||
if (isset($tableColumn['collation']) && $tableColumn['collation'] !== 'NULL') {
|
||||
$column->setPlatformOption('collation', $tableColumn['collation']);
|
||||
}
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
private function parseDefaultExpression(string $value): ?string
|
||||
{
|
||||
while (preg_match('/^\((.*)\)$/s', $value, $matches)) {
|
||||
$value = $matches[1];
|
||||
}
|
||||
|
||||
if ($value === 'NULL') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (preg_match('/^\'(.*)\'$/s', $value, $matches) === 1) {
|
||||
$value = str_replace("''", "'", $matches[1]);
|
||||
}
|
||||
|
||||
if ($value === 'getdate()') {
|
||||
return $this->_platform->getCurrentTimestampSQL();
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeysList($tableForeignKeys)
|
||||
{
|
||||
$foreignKeys = [];
|
||||
|
||||
foreach ($tableForeignKeys as $tableForeignKey) {
|
||||
$name = $tableForeignKey['ForeignKey'];
|
||||
|
||||
if (! isset($foreignKeys[$name])) {
|
||||
$foreignKeys[$name] = [
|
||||
'local_columns' => [$tableForeignKey['ColumnName']],
|
||||
'foreign_table' => $tableForeignKey['ReferenceTableName'],
|
||||
'foreign_columns' => [$tableForeignKey['ReferenceColumnName']],
|
||||
'name' => $name,
|
||||
'options' => [
|
||||
'onUpdate' => str_replace('_', ' ', $tableForeignKey['update_referential_action_desc']),
|
||||
'onDelete' => str_replace('_', ' ', $tableForeignKey['delete_referential_action_desc']),
|
||||
],
|
||||
];
|
||||
} else {
|
||||
$foreignKeys[$name]['local_columns'][] = $tableForeignKey['ColumnName'];
|
||||
$foreignKeys[$name]['foreign_columns'][] = $tableForeignKey['ReferenceColumnName'];
|
||||
}
|
||||
}
|
||||
|
||||
return parent::_getPortableTableForeignKeysList($foreignKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
|
||||
{
|
||||
foreach ($tableIndexes as &$tableIndex) {
|
||||
$tableIndex['non_unique'] = (bool) $tableIndex['non_unique'];
|
||||
$tableIndex['primary'] = (bool) $tableIndex['primary'];
|
||||
$tableIndex['flags'] = $tableIndex['flags'] ? [$tableIndex['flags']] : null;
|
||||
}
|
||||
|
||||
return parent::_getPortableTableIndexesList($tableIndexes, $tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
|
||||
{
|
||||
return new ForeignKeyConstraint(
|
||||
$tableForeignKey['local_columns'],
|
||||
$tableForeignKey['foreign_table'],
|
||||
$tableForeignKey['foreign_columns'],
|
||||
$tableForeignKey['name'],
|
||||
$tableForeignKey['options'],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableDefinition($table)
|
||||
{
|
||||
if ($table['schema_name'] !== 'dbo') {
|
||||
return $table['schema_name'] . '.' . $table['table_name'];
|
||||
}
|
||||
|
||||
return $table['table_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableDatabaseDefinition($database)
|
||||
{
|
||||
return $database['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see listSchemaNames()} instead.
|
||||
*/
|
||||
protected function getPortableNamespaceDefinition(array $namespace)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/issues/4503',
|
||||
'SQLServerSchemaManager::getPortableNamespaceDefinition() is deprecated,'
|
||||
. ' use SQLServerSchemaManager::listSchemaNames() instead.',
|
||||
);
|
||||
|
||||
return $namespace['name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableViewDefinition($view)
|
||||
{
|
||||
// @todo
|
||||
return new View($view['name'], $view['definition']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function alterTable(TableDiff $tableDiff)
|
||||
{
|
||||
$droppedColumns = $tableDiff->getDroppedColumns();
|
||||
|
||||
if (count($droppedColumns) > 0) {
|
||||
$tableName = ($tableDiff->getOldTable() ?? $tableDiff->getName($this->_platform))->getName();
|
||||
|
||||
foreach ($droppedColumns as $col) {
|
||||
foreach ($this->getColumnConstraints($tableName, $col->getName()) as $constraint) {
|
||||
$this->_conn->executeStatement(
|
||||
sprintf(
|
||||
'ALTER TABLE %s DROP CONSTRAINT %s',
|
||||
$tableName,
|
||||
$constraint,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
parent::alterTable($tableDiff);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of the constraints for a given column.
|
||||
*
|
||||
* @return iterable<string>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getColumnConstraints(string $table, string $column): iterable
|
||||
{
|
||||
return $this->_conn->iterateColumn(
|
||||
<<<'SQL'
|
||||
SELECT o.name
|
||||
FROM sys.objects o
|
||||
INNER JOIN sys.objects t
|
||||
ON t.object_id = o.parent_object_id
|
||||
AND t.type = 'U'
|
||||
INNER JOIN sys.default_constraints dc
|
||||
ON dc.object_id = o.object_id
|
||||
INNER JOIN sys.columns c
|
||||
ON c.column_id = dc.parent_column_id
|
||||
AND c.object_id = t.object_id
|
||||
WHERE t.name = ?
|
||||
AND c.name = ?
|
||||
SQL
|
||||
,
|
||||
[$table, $column],
|
||||
);
|
||||
}
|
||||
|
||||
/** @throws Exception */
|
||||
public function createComparator(): Comparator
|
||||
{
|
||||
return new SQLServer\Comparator($this->_platform, $this->getDatabaseCollation());
|
||||
}
|
||||
|
||||
/** @throws Exception */
|
||||
private function getDatabaseCollation(): string
|
||||
{
|
||||
if ($this->databaseCollation === null) {
|
||||
$databaseCollation = $this->_conn->fetchOne(
|
||||
'SELECT collation_name FROM sys.databases WHERE name = '
|
||||
. $this->_platform->getCurrentDatabaseExpression(),
|
||||
);
|
||||
|
||||
// a database is always selected, even if omitted in the connection parameters
|
||||
assert(is_string($databaseCollation));
|
||||
|
||||
$this->databaseCollation = $databaseCollation;
|
||||
}
|
||||
|
||||
return $this->databaseCollation;
|
||||
}
|
||||
|
||||
protected function selectTableNames(string $databaseName): Result
|
||||
{
|
||||
// The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams
|
||||
$sql = <<<'SQL'
|
||||
SELECT name AS table_name,
|
||||
SCHEMA_NAME(schema_id) AS schema_name
|
||||
FROM sys.objects
|
||||
WHERE type = 'U'
|
||||
AND name != 'sysdiagrams'
|
||||
ORDER BY name
|
||||
SQL;
|
||||
|
||||
return $this->_conn->executeQuery($sql);
|
||||
}
|
||||
|
||||
protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' obj.name AS table_name, scm.name AS schema_name,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
col.name,
|
||||
type.name AS type,
|
||||
col.max_length AS length,
|
||||
~col.is_nullable AS notnull,
|
||||
def.definition AS [default],
|
||||
col.scale,
|
||||
col.precision,
|
||||
col.is_identity AS autoincrement,
|
||||
col.collation_name AS collation,
|
||||
-- CAST avoids driver error for sql_variant type
|
||||
CAST(prop.value AS NVARCHAR(MAX)) AS comment
|
||||
FROM sys.columns AS col
|
||||
JOIN sys.types AS type
|
||||
ON col.user_type_id = type.user_type_id
|
||||
JOIN sys.objects AS obj
|
||||
ON col.object_id = obj.object_id
|
||||
JOIN sys.schemas AS scm
|
||||
ON obj.schema_id = scm.schema_id
|
||||
LEFT JOIN sys.default_constraints def
|
||||
ON col.default_object_id = def.object_id
|
||||
AND col.object_id = def.parent_object_id
|
||||
LEFT JOIN sys.extended_properties AS prop
|
||||
ON obj.object_id = prop.major_id
|
||||
AND col.column_id = prop.minor_id
|
||||
AND prop.name = 'MS_Description'
|
||||
SQL;
|
||||
|
||||
// The "sysdiagrams" table must be ignored as it's internal SQL Server table for Database Diagrams
|
||||
$conditions = ["obj.type = 'U'", "obj.name != 'sysdiagrams'"];
|
||||
$params = [];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'obj.name');
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions);
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' tbl.name AS table_name, scm.name AS schema_name,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
idx.name AS key_name,
|
||||
col.name AS column_name,
|
||||
~idx.is_unique AS non_unique,
|
||||
idx.is_primary_key AS [primary],
|
||||
CASE idx.type
|
||||
WHEN '1' THEN 'clustered'
|
||||
WHEN '2' THEN 'nonclustered'
|
||||
ELSE NULL
|
||||
END AS flags
|
||||
FROM sys.tables AS tbl
|
||||
JOIN sys.schemas AS scm
|
||||
ON tbl.schema_id = scm.schema_id
|
||||
JOIN sys.indexes AS idx
|
||||
ON tbl.object_id = idx.object_id
|
||||
JOIN sys.index_columns AS idxcol
|
||||
ON idx.object_id = idxcol.object_id
|
||||
AND idx.index_id = idxcol.index_id
|
||||
JOIN sys.columns AS col
|
||||
ON idxcol.object_id = col.object_id
|
||||
AND idxcol.column_id = col.column_id
|
||||
SQL;
|
||||
|
||||
$conditions = [];
|
||||
$params = [];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = $this->getTableWhereClause($tableName, 'scm.name', 'tbl.name');
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions);
|
||||
}
|
||||
|
||||
$sql .= ' ORDER BY idx.index_id, idxcol.key_ordinal';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = 'SELECT';
|
||||
|
||||
if ($tableName === null) {
|
||||
$sql .= ' OBJECT_NAME (f.parent_object_id) AS table_name, SCHEMA_NAME(f.schema_id) AS schema_name,';
|
||||
}
|
||||
|
||||
$sql .= <<<'SQL'
|
||||
f.name AS ForeignKey,
|
||||
SCHEMA_NAME (f.SCHEMA_ID) AS SchemaName,
|
||||
OBJECT_NAME (f.parent_object_id) AS TableName,
|
||||
COL_NAME (fc.parent_object_id,fc.parent_column_id) AS ColumnName,
|
||||
SCHEMA_NAME (o.SCHEMA_ID) ReferenceSchemaName,
|
||||
OBJECT_NAME (f.referenced_object_id) AS ReferenceTableName,
|
||||
COL_NAME(fc.referenced_object_id,fc.referenced_column_id) AS ReferenceColumnName,
|
||||
f.delete_referential_action_desc,
|
||||
f.update_referential_action_desc
|
||||
FROM sys.foreign_keys AS f
|
||||
INNER JOIN sys.foreign_key_columns AS fc
|
||||
INNER JOIN sys.objects AS o ON o.OBJECT_ID = fc.referenced_object_id
|
||||
ON f.OBJECT_ID = fc.constraint_object_id
|
||||
SQL;
|
||||
|
||||
$conditions = [];
|
||||
$params = [];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = $this->getTableWhereClause(
|
||||
$tableName,
|
||||
'SCHEMA_NAME(f.schema_id)',
|
||||
'OBJECT_NAME(f.parent_object_id)',
|
||||
);
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions);
|
||||
}
|
||||
|
||||
$sql .= ' ORDER BY fc.constraint_column_id';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT
|
||||
tbl.name,
|
||||
p.value AS [table_comment]
|
||||
FROM
|
||||
sys.tables AS tbl
|
||||
INNER JOIN sys.extended_properties AS p ON p.major_id=tbl.object_id AND p.minor_id=0 AND p.class=1
|
||||
SQL;
|
||||
|
||||
$conditions = ["SCHEMA_NAME(tbl.schema_id) = N'dbo'", "p.name = N'MS_Description'"];
|
||||
$params = [];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = "tbl.name = N'" . $tableName . "'";
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions);
|
||||
|
||||
/** @var array<string,array<string,mixed>> $metadata */
|
||||
$metadata = $this->_conn->executeQuery($sql, $params)
|
||||
->fetchAllAssociativeIndexed();
|
||||
|
||||
$tableOptions = [];
|
||||
foreach ($metadata as $table => $data) {
|
||||
$data = array_change_key_case($data, CASE_LOWER);
|
||||
|
||||
$tableOptions[$table] = [
|
||||
'comment' => $data['table_comment'],
|
||||
];
|
||||
}
|
||||
|
||||
return $tableOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the where clause to filter schema and table name in a query.
|
||||
*
|
||||
* @param string $table The full qualified name of the table.
|
||||
* @param string $schemaColumn The name of the column to compare the schema to in the where clause.
|
||||
* @param string $tableColumn The name of the column to compare the table to in the where clause.
|
||||
*/
|
||||
private function getTableWhereClause($table, $schemaColumn, $tableColumn): string
|
||||
{
|
||||
if (strpos($table, '.') !== false) {
|
||||
[$schema, $table] = explode('.', $table);
|
||||
$schema = $this->_platform->quoteStringLiteral($schema);
|
||||
$table = $this->_platform->quoteStringLiteral($table);
|
||||
} else {
|
||||
$schema = 'SCHEMA_NAME()';
|
||||
$table = $this->_platform->quoteStringLiteral($table);
|
||||
}
|
||||
|
||||
return sprintf('(%s = %s AND %s = %s)', $tableColumn, $table, $schemaColumn, $schema);
|
||||
}
|
||||
}
|
||||
+523
@@ -0,0 +1,523 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Schema\Visitor\NamespaceVisitor;
|
||||
use Doctrine\DBAL\Schema\Visitor\Visitor;
|
||||
use Doctrine\DBAL\SQL\Builder\CreateSchemaObjectsSQLBuilder;
|
||||
use Doctrine\DBAL\SQL\Builder\DropSchemaObjectsSQLBuilder;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_keys;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
* Object representation of a database schema.
|
||||
*
|
||||
* Different vendors have very inconsistent naming with regard to the concept
|
||||
* of a "schema". Doctrine understands a schema as the entity that conceptually
|
||||
* wraps a set of database objects such as tables, sequences, indexes and
|
||||
* foreign keys that belong to each other into a namespace. A Doctrine Schema
|
||||
* has nothing to do with the "SCHEMA" defined as in PostgreSQL, it is more
|
||||
* related to the concept of "DATABASE" that exists in MySQL and PostgreSQL.
|
||||
*
|
||||
* Every asset in the doctrine schema has a name. A name consists of either a
|
||||
* namespace.local name pair or just a local unqualified name.
|
||||
*
|
||||
* The abstraction layer that covers a PostgreSQL schema is the namespace of an
|
||||
* database object (asset). A schema can have a name, which will be used as
|
||||
* default namespace for the unqualified database objects that are created in
|
||||
* the schema.
|
||||
*
|
||||
* In the case of MySQL where cross-database queries are allowed this leads to
|
||||
* databases being "misinterpreted" as namespaces. This is intentional, however
|
||||
* the CREATE/DROP SQL visitors will just filter this queries and do not
|
||||
* execute them. Only the queries for the currently connected database are
|
||||
* executed.
|
||||
*/
|
||||
class Schema extends AbstractAsset
|
||||
{
|
||||
/**
|
||||
* The namespaces in this schema.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private array $namespaces = [];
|
||||
|
||||
/** @var Table[] */
|
||||
protected $_tables = [];
|
||||
|
||||
/** @var Sequence[] */
|
||||
protected $_sequences = [];
|
||||
|
||||
/** @var SchemaConfig */
|
||||
protected $_schemaConfig;
|
||||
|
||||
/**
|
||||
* @param Table[] $tables
|
||||
* @param Sequence[] $sequences
|
||||
* @param string[] $namespaces
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function __construct(
|
||||
array $tables = [],
|
||||
array $sequences = [],
|
||||
?SchemaConfig $schemaConfig = null,
|
||||
array $namespaces = []
|
||||
) {
|
||||
$schemaConfig ??= new SchemaConfig();
|
||||
|
||||
$this->_schemaConfig = $schemaConfig;
|
||||
$this->_setName($schemaConfig->getName() ?? 'public');
|
||||
|
||||
foreach ($namespaces as $namespace) {
|
||||
$this->createNamespace($namespace);
|
||||
}
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$this->_addTable($table);
|
||||
}
|
||||
|
||||
foreach ($sequences as $sequence) {
|
||||
$this->_addSequence($sequence);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasExplicitForeignKeyIndexes()
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4822',
|
||||
'Schema::hasExplicitForeignKeyIndexes() is deprecated.',
|
||||
);
|
||||
|
||||
return $this->_schemaConfig->hasExplicitForeignKeyIndexes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
protected function _addTable(Table $table)
|
||||
{
|
||||
$namespaceName = $table->getNamespaceName();
|
||||
$tableName = $this->normalizeName($table);
|
||||
|
||||
if (isset($this->_tables[$tableName])) {
|
||||
throw SchemaException::tableAlreadyExists($tableName);
|
||||
}
|
||||
|
||||
if (
|
||||
$namespaceName !== null
|
||||
&& ! $table->isInDefaultNamespace($this->getName())
|
||||
&& ! $this->hasNamespace($namespaceName)
|
||||
) {
|
||||
$this->createNamespace($namespaceName);
|
||||
}
|
||||
|
||||
$this->_tables[$tableName] = $table;
|
||||
$table->setSchemaConfig($this->_schemaConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
protected function _addSequence(Sequence $sequence)
|
||||
{
|
||||
$namespaceName = $sequence->getNamespaceName();
|
||||
$seqName = $this->normalizeName($sequence);
|
||||
|
||||
if (isset($this->_sequences[$seqName])) {
|
||||
throw SchemaException::sequenceAlreadyExists($seqName);
|
||||
}
|
||||
|
||||
if (
|
||||
$namespaceName !== null
|
||||
&& ! $sequence->isInDefaultNamespace($this->getName())
|
||||
&& ! $this->hasNamespace($namespaceName)
|
||||
) {
|
||||
$this->createNamespace($namespaceName);
|
||||
}
|
||||
|
||||
$this->_sequences[$seqName] = $sequence;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the namespaces of this schema.
|
||||
*
|
||||
* @return string[] A list of namespace names.
|
||||
*/
|
||||
public function getNamespaces()
|
||||
{
|
||||
return $this->namespaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all tables of this schema.
|
||||
*
|
||||
* @return Table[]
|
||||
*/
|
||||
public function getTables()
|
||||
{
|
||||
return $this->_tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return Table
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function getTable($name)
|
||||
{
|
||||
$name = $this->getFullQualifiedAssetName($name);
|
||||
if (! isset($this->_tables[$name])) {
|
||||
throw SchemaException::tableDoesNotExist($name);
|
||||
}
|
||||
|
||||
return $this->_tables[$name];
|
||||
}
|
||||
|
||||
/** @param string $name */
|
||||
private function getFullQualifiedAssetName($name): string
|
||||
{
|
||||
$name = $this->getUnquotedAssetName($name);
|
||||
|
||||
if (strpos($name, '.') === false) {
|
||||
$name = $this->getName() . '.' . $name;
|
||||
}
|
||||
|
||||
return strtolower($name);
|
||||
}
|
||||
|
||||
private function normalizeName(AbstractAsset $asset): string
|
||||
{
|
||||
return $asset->getFullQualifiedName($this->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the unquoted representation of a given asset name.
|
||||
*
|
||||
* @param string $assetName Quoted or unquoted representation of an asset name.
|
||||
*/
|
||||
private function getUnquotedAssetName($assetName): string
|
||||
{
|
||||
if ($this->isIdentifierQuoted($assetName)) {
|
||||
return $this->trimQuotes($assetName);
|
||||
}
|
||||
|
||||
return $assetName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this schema have a namespace with the given name?
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasNamespace($name)
|
||||
{
|
||||
$name = strtolower($this->getUnquotedAssetName($name));
|
||||
|
||||
return isset($this->namespaces[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this schema have a table with the given name?
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTable($name)
|
||||
{
|
||||
$name = $this->getFullQualifiedAssetName($name);
|
||||
|
||||
return isset($this->_tables[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all table names, prefixed with a schema name, even the default one if present.
|
||||
*
|
||||
* @deprecated Use {@see getTables()} and {@see Table::getName()} instead.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getTableNames()
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4800',
|
||||
'Schema::getTableNames() is deprecated.'
|
||||
. ' Use Schema::getTables() and Table::getName() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return array_keys($this->_tables);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSequence($name)
|
||||
{
|
||||
$name = $this->getFullQualifiedAssetName($name);
|
||||
|
||||
return isset($this->_sequences[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return Sequence
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function getSequence($name)
|
||||
{
|
||||
$name = $this->getFullQualifiedAssetName($name);
|
||||
if (! $this->hasSequence($name)) {
|
||||
throw SchemaException::sequenceDoesNotExist($name);
|
||||
}
|
||||
|
||||
return $this->_sequences[$name];
|
||||
}
|
||||
|
||||
/** @return Sequence[] */
|
||||
public function getSequences()
|
||||
{
|
||||
return $this->_sequences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new namespace.
|
||||
*
|
||||
* @param string $name The name of the namespace to create.
|
||||
*
|
||||
* @return Schema This schema instance.
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function createNamespace($name)
|
||||
{
|
||||
$unquotedName = strtolower($this->getUnquotedAssetName($name));
|
||||
|
||||
if (isset($this->namespaces[$unquotedName])) {
|
||||
throw SchemaException::namespaceAlreadyExists($unquotedName);
|
||||
}
|
||||
|
||||
$this->namespaces[$unquotedName] = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new table.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return Table
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function createTable($name)
|
||||
{
|
||||
$table = new Table($name);
|
||||
$this->_addTable($table);
|
||||
|
||||
foreach ($this->_schemaConfig->getDefaultTableOptions() as $option => $value) {
|
||||
$table->addOption($option, $value);
|
||||
}
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renames a table.
|
||||
*
|
||||
* @param string $oldName
|
||||
* @param string $newName
|
||||
*
|
||||
* @return Schema
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function renameTable($oldName, $newName)
|
||||
{
|
||||
$table = $this->getTable($oldName);
|
||||
$table->_setName($newName);
|
||||
|
||||
$this->dropTable($oldName);
|
||||
$this->_addTable($table);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drops a table from the schema.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return Schema
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function dropTable($name)
|
||||
{
|
||||
$name = $this->getFullQualifiedAssetName($name);
|
||||
$this->getTable($name);
|
||||
unset($this->_tables[$name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new sequence.
|
||||
*
|
||||
* @param string $name
|
||||
* @param int $allocationSize
|
||||
* @param int $initialValue
|
||||
*
|
||||
* @return Sequence
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function createSequence($name, $allocationSize = 1, $initialValue = 1)
|
||||
{
|
||||
$seq = new Sequence($name, $allocationSize, $initialValue);
|
||||
$this->_addSequence($seq);
|
||||
|
||||
return $seq;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return Schema
|
||||
*/
|
||||
public function dropSequence($name)
|
||||
{
|
||||
$name = $this->getFullQualifiedAssetName($name);
|
||||
unset($this->_sequences[$name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of necessary SQL queries to create the schema on the given platform.
|
||||
*
|
||||
* @return list<string>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function toSql(AbstractPlatform $platform)
|
||||
{
|
||||
$builder = new CreateSchemaObjectsSQLBuilder($platform);
|
||||
|
||||
return $builder->buildSQL($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of necessary SQL queries to drop the schema on the given platform.
|
||||
*
|
||||
* @return list<string>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function toDropSql(AbstractPlatform $platform)
|
||||
{
|
||||
$builder = new DropSchemaObjectsSQLBuilder($platform);
|
||||
|
||||
return $builder->buildSQL($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @return string[]
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform)
|
||||
{
|
||||
$schemaDiff = (new Comparator())->compareSchemas($this, $toSchema);
|
||||
|
||||
return $schemaDiff->toSql($platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @return string[]
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform)
|
||||
{
|
||||
$schemaDiff = (new Comparator())->compareSchemas($fromSchema, $this);
|
||||
|
||||
return $schemaDiff->toSql($platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function visit(Visitor $visitor)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5435',
|
||||
'Schema::visit() is deprecated.',
|
||||
);
|
||||
|
||||
$visitor->acceptSchema($this);
|
||||
|
||||
if ($visitor instanceof NamespaceVisitor) {
|
||||
foreach ($this->namespaces as $namespace) {
|
||||
$visitor->acceptNamespace($namespace);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->_tables as $table) {
|
||||
$table->visit($visitor);
|
||||
}
|
||||
|
||||
foreach ($this->_sequences as $sequence) {
|
||||
$sequence->visit($visitor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cloning a Schema triggers a deep clone of all related assets.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __clone()
|
||||
{
|
||||
foreach ($this->_tables as $k => $table) {
|
||||
$this->_tables[$k] = clone $table;
|
||||
}
|
||||
|
||||
foreach ($this->_sequences as $k => $sequence) {
|
||||
$this->_sequences[$k] = clone $sequence;
|
||||
}
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
/**
|
||||
* Configuration for a Schema.
|
||||
*/
|
||||
class SchemaConfig
|
||||
{
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasExplicitForeignKeyIndexes = false;
|
||||
|
||||
/** @var int */
|
||||
protected $maxIdentifierLength = 63;
|
||||
|
||||
/** @var string|null */
|
||||
protected $name;
|
||||
|
||||
/** @var mixed[] */
|
||||
protected $defaultTableOptions = [];
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasExplicitForeignKeyIndexes()
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4822',
|
||||
'SchemaConfig::hasExplicitForeignKeyIndexes() is deprecated.',
|
||||
);
|
||||
|
||||
return $this->hasExplicitForeignKeyIndexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @param bool $flag
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setExplicitForeignKeyIndexes($flag)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4822',
|
||||
'SchemaConfig::setExplicitForeignKeyIndexes() is deprecated.',
|
||||
);
|
||||
|
||||
$this->hasExplicitForeignKeyIndexes = (bool) $flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $length
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setMaxIdentifierLength($length)
|
||||
{
|
||||
$this->maxIdentifierLength = (int) $length;
|
||||
}
|
||||
|
||||
/** @return int */
|
||||
public function getMaxIdentifierLength()
|
||||
{
|
||||
return $this->maxIdentifierLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default namespace of schema objects.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default namespace name of schema objects.
|
||||
*
|
||||
* @param string $name The value to set.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setName($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default options that are passed to Table instances created with
|
||||
* Schema#createTable().
|
||||
*
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getDefaultTableOptions()
|
||||
{
|
||||
return $this->defaultTableOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $defaultTableOptions
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setDefaultTableOptions(array $defaultTableOptions)
|
||||
{
|
||||
$this->defaultTableOptions = $defaultTableOptions;
|
||||
}
|
||||
}
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_filter;
|
||||
use function array_merge;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Differences between two schemas.
|
||||
*
|
||||
* The object contains the operations to change the schema stored in $fromSchema
|
||||
* to a target schema.
|
||||
*/
|
||||
class SchemaDiff
|
||||
{
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @var Schema|null
|
||||
*/
|
||||
public $fromSchema;
|
||||
|
||||
/**
|
||||
* All added namespaces.
|
||||
*
|
||||
* @internal Use {@link getCreatedSchemas()} instead.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $newNamespaces = [];
|
||||
|
||||
/**
|
||||
* All removed namespaces.
|
||||
*
|
||||
* @internal Use {@link getDroppedSchemas()} instead.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
public $removedNamespaces = [];
|
||||
|
||||
/**
|
||||
* All added tables.
|
||||
*
|
||||
* @internal Use {@link getCreatedTables()} instead.
|
||||
*
|
||||
* @var Table[]
|
||||
*/
|
||||
public $newTables = [];
|
||||
|
||||
/**
|
||||
* All changed tables.
|
||||
*
|
||||
* @internal Use {@link getAlteredTables()} instead.
|
||||
*
|
||||
* @var TableDiff[]
|
||||
*/
|
||||
public $changedTables = [];
|
||||
|
||||
/**
|
||||
* All removed tables.
|
||||
*
|
||||
* @internal Use {@link getDroppedTables()} instead.
|
||||
*
|
||||
* @var Table[]
|
||||
*/
|
||||
public $removedTables = [];
|
||||
|
||||
/**
|
||||
* @internal Use {@link getCreatedSequences()} instead.
|
||||
*
|
||||
* @var Sequence[]
|
||||
*/
|
||||
public $newSequences = [];
|
||||
|
||||
/**
|
||||
* @internal Use {@link getAlteredSequences()} instead.
|
||||
*
|
||||
* @var Sequence[]
|
||||
*/
|
||||
public $changedSequences = [];
|
||||
|
||||
/**
|
||||
* @internal Use {@link getDroppedSequences()} instead.
|
||||
*
|
||||
* @var Sequence[]
|
||||
*/
|
||||
public $removedSequences = [];
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @var ForeignKeyConstraint[]
|
||||
*/
|
||||
public $orphanedForeignKeys = [];
|
||||
|
||||
/**
|
||||
* Constructs an SchemaDiff object.
|
||||
*
|
||||
* @internal The diff can be only instantiated by a {@see Comparator}.
|
||||
*
|
||||
* @param Table[] $newTables
|
||||
* @param TableDiff[] $changedTables
|
||||
* @param Table[] $removedTables
|
||||
* @param array<string> $createdSchemas
|
||||
* @param array<string> $droppedSchemas
|
||||
* @param array<Sequence> $createdSequences
|
||||
* @param array<Sequence> $alteredSequences
|
||||
* @param array<Sequence> $droppedSequences
|
||||
*/
|
||||
public function __construct(
|
||||
$newTables = [],
|
||||
$changedTables = [],
|
||||
$removedTables = [],
|
||||
?Schema $fromSchema = null,
|
||||
$createdSchemas = [],
|
||||
$droppedSchemas = [],
|
||||
$createdSequences = [],
|
||||
$alteredSequences = [],
|
||||
$droppedSequences = []
|
||||
) {
|
||||
$this->newTables = $newTables;
|
||||
|
||||
$this->changedTables = array_filter($changedTables, static function (TableDiff $diff): bool {
|
||||
return ! $diff->isEmpty();
|
||||
});
|
||||
|
||||
$this->removedTables = $removedTables;
|
||||
$this->fromSchema = $fromSchema;
|
||||
$this->newNamespaces = $createdSchemas;
|
||||
$this->removedNamespaces = $droppedSchemas;
|
||||
$this->newSequences = $createdSequences;
|
||||
$this->changedSequences = $alteredSequences;
|
||||
$this->removedSequences = $droppedSequences;
|
||||
}
|
||||
|
||||
/** @return array<string> */
|
||||
public function getCreatedSchemas(): array
|
||||
{
|
||||
return $this->newNamespaces;
|
||||
}
|
||||
|
||||
/** @return array<string> */
|
||||
public function getDroppedSchemas(): array
|
||||
{
|
||||
return $this->removedNamespaces;
|
||||
}
|
||||
|
||||
/** @return array<Table> */
|
||||
public function getCreatedTables(): array
|
||||
{
|
||||
return $this->newTables;
|
||||
}
|
||||
|
||||
/** @return array<TableDiff> */
|
||||
public function getAlteredTables(): array
|
||||
{
|
||||
return $this->changedTables;
|
||||
}
|
||||
|
||||
/** @return array<Table> */
|
||||
public function getDroppedTables(): array
|
||||
{
|
||||
return $this->removedTables;
|
||||
}
|
||||
|
||||
/** @return array<Sequence> */
|
||||
public function getCreatedSequences(): array
|
||||
{
|
||||
return $this->newSequences;
|
||||
}
|
||||
|
||||
/** @return array<Sequence> */
|
||||
public function getAlteredSequences(): array
|
||||
{
|
||||
return $this->changedSequences;
|
||||
}
|
||||
|
||||
/** @return array<Sequence> */
|
||||
public function getDroppedSequences(): array
|
||||
{
|
||||
return $this->removedSequences;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the diff is empty (contains no changes).
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return count($this->newNamespaces) === 0
|
||||
&& count($this->removedNamespaces) === 0
|
||||
&& count($this->newTables) === 0
|
||||
&& count($this->changedTables) === 0
|
||||
&& count($this->removedTables) === 0
|
||||
&& count($this->newSequences) === 0
|
||||
&& count($this->changedSequences) === 0
|
||||
&& count($this->removedSequences) === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The to save sql mode ensures that the following things don't happen:
|
||||
*
|
||||
* 1. Tables are deleted
|
||||
* 2. Sequences are deleted
|
||||
* 3. Foreign Keys which reference tables that would otherwise be deleted.
|
||||
*
|
||||
* This way it is ensured that assets are deleted which might not be relevant to the metadata schema at all.
|
||||
*
|
||||
* @deprecated
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function toSaveSql(AbstractPlatform $platform)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5766',
|
||||
'%s is deprecated.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return $this->_toSql($platform, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link AbstractPlatform::getAlterSchemaSQL()} instead.
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
public function toSql(AbstractPlatform $platform)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5766',
|
||||
'%s is deprecated. Use AbstractPlatform::getAlterSchemaSQL() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return $this->_toSql($platform, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $saveMode
|
||||
*
|
||||
* @return list<string>
|
||||
*/
|
||||
protected function _toSql(AbstractPlatform $platform, $saveMode = false)
|
||||
{
|
||||
$sql = [];
|
||||
|
||||
if ($platform->supportsSchemas()) {
|
||||
foreach ($this->getCreatedSchemas() as $schema) {
|
||||
$sql[] = $platform->getCreateSchemaSQL($schema);
|
||||
}
|
||||
}
|
||||
|
||||
if ($platform->supportsForeignKeyConstraints() && $saveMode === false) {
|
||||
foreach ($this->orphanedForeignKeys as $orphanedForeignKey) {
|
||||
$sql[] = $platform->getDropForeignKeySQL($orphanedForeignKey, $orphanedForeignKey->getLocalTable());
|
||||
}
|
||||
}
|
||||
|
||||
if ($platform->supportsSequences() === true) {
|
||||
foreach ($this->getAlteredSequences() as $sequence) {
|
||||
$sql[] = $platform->getAlterSequenceSQL($sequence);
|
||||
}
|
||||
|
||||
if ($saveMode === false) {
|
||||
foreach ($this->getDroppedSequences() as $sequence) {
|
||||
$sql[] = $platform->getDropSequenceSQL($sequence);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->getCreatedSequences() as $sequence) {
|
||||
$sql[] = $platform->getCreateSequenceSQL($sequence);
|
||||
}
|
||||
}
|
||||
|
||||
$sql = array_merge($sql, $platform->getCreateTablesSQL($this->getCreatedTables()));
|
||||
|
||||
if ($saveMode === false) {
|
||||
$sql = array_merge($sql, $platform->getDropTablesSQL($this->getDroppedTables()));
|
||||
}
|
||||
|
||||
foreach ($this->getAlteredTables() as $tableDiff) {
|
||||
$sql = array_merge($sql, $platform->getAlterTableSQL($tableDiff));
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
+204
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Schema\Exception\ColumnAlreadyExists;
|
||||
use Doctrine\DBAL\Schema\Exception\ColumnDoesNotExist;
|
||||
use Doctrine\DBAL\Schema\Exception\ForeignKeyDoesNotExist;
|
||||
use Doctrine\DBAL\Schema\Exception\IndexAlreadyExists;
|
||||
use Doctrine\DBAL\Schema\Exception\IndexDoesNotExist;
|
||||
use Doctrine\DBAL\Schema\Exception\IndexNameInvalid;
|
||||
use Doctrine\DBAL\Schema\Exception\NamedForeignKeyRequired;
|
||||
use Doctrine\DBAL\Schema\Exception\NamespaceAlreadyExists;
|
||||
use Doctrine\DBAL\Schema\Exception\SequenceAlreadyExists;
|
||||
use Doctrine\DBAL\Schema\Exception\SequenceDoesNotExist;
|
||||
use Doctrine\DBAL\Schema\Exception\TableAlreadyExists;
|
||||
use Doctrine\DBAL\Schema\Exception\TableDoesNotExist;
|
||||
use Doctrine\DBAL\Schema\Exception\UniqueConstraintDoesNotExist;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/** @psalm-immutable */
|
||||
class SchemaException extends Exception
|
||||
{
|
||||
/** @deprecated Use {@see TableDoesNotExist} instead. */
|
||||
public const TABLE_DOESNT_EXIST = 10;
|
||||
|
||||
/** @deprecated Use {@see TableAlreadyExists} instead. */
|
||||
public const TABLE_ALREADY_EXISTS = 20;
|
||||
|
||||
/** @deprecated Use {@see ColumnDoesNotExist} instead. */
|
||||
public const COLUMN_DOESNT_EXIST = 30;
|
||||
|
||||
/** @deprecated Use {@see ColumnAlreadyExists} instead. */
|
||||
public const COLUMN_ALREADY_EXISTS = 40;
|
||||
|
||||
/** @deprecated Use {@see IndexDoesNotExist} instead. */
|
||||
public const INDEX_DOESNT_EXIST = 50;
|
||||
|
||||
/** @deprecated Use {@see IndexAlreadyExists} instead. */
|
||||
public const INDEX_ALREADY_EXISTS = 60;
|
||||
|
||||
/** @deprecated Use {@see SequenceDoesNotExist} instead. */
|
||||
public const SEQUENCE_DOENST_EXIST = 70;
|
||||
|
||||
/** @deprecated Use {@see SequenceAlreadyExists} instead. */
|
||||
public const SEQUENCE_ALREADY_EXISTS = 80;
|
||||
|
||||
/** @deprecated Use {@see IndexNameInvalid} instead. */
|
||||
public const INDEX_INVALID_NAME = 90;
|
||||
|
||||
/** @deprecated Use {@see ForeignKeyDoesNotExist} instead. */
|
||||
public const FOREIGNKEY_DOESNT_EXIST = 100;
|
||||
|
||||
/** @deprecated Use {@see UniqueConstraintDoesNotExist} instead. */
|
||||
public const CONSTRAINT_DOESNT_EXIST = 110;
|
||||
|
||||
/** @deprecated Use {@see NamespaceAlreadyExists} instead. */
|
||||
public const NAMESPACE_ALREADY_EXISTS = 120;
|
||||
|
||||
/**
|
||||
* @param string $tableName
|
||||
*
|
||||
* @return SchemaException
|
||||
*/
|
||||
public static function tableDoesNotExist($tableName)
|
||||
{
|
||||
return TableDoesNotExist::new($tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $indexName
|
||||
*
|
||||
* @return SchemaException
|
||||
*/
|
||||
public static function indexNameInvalid($indexName)
|
||||
{
|
||||
return IndexNameInvalid::new($indexName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $indexName
|
||||
* @param string $table
|
||||
*
|
||||
* @return SchemaException
|
||||
*/
|
||||
public static function indexDoesNotExist($indexName, $table)
|
||||
{
|
||||
return IndexDoesNotExist::new($indexName, $table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $indexName
|
||||
* @param string $table
|
||||
*
|
||||
* @return SchemaException
|
||||
*/
|
||||
public static function indexAlreadyExists($indexName, $table)
|
||||
{
|
||||
return IndexAlreadyExists::new($indexName, $table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $columnName
|
||||
* @param string $table
|
||||
*
|
||||
* @return SchemaException
|
||||
*/
|
||||
public static function columnDoesNotExist($columnName, $table)
|
||||
{
|
||||
return ColumnDoesNotExist::new($columnName, $table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $namespaceName
|
||||
*
|
||||
* @return SchemaException
|
||||
*/
|
||||
public static function namespaceAlreadyExists($namespaceName)
|
||||
{
|
||||
return NamespaceAlreadyExists::new($namespaceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tableName
|
||||
*
|
||||
* @return SchemaException
|
||||
*/
|
||||
public static function tableAlreadyExists($tableName)
|
||||
{
|
||||
return TableAlreadyExists::new($tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $tableName
|
||||
* @param string $columnName
|
||||
*
|
||||
* @return SchemaException
|
||||
*/
|
||||
public static function columnAlreadyExists($tableName, $columnName)
|
||||
{
|
||||
return ColumnAlreadyExists::new($tableName, $columnName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return SchemaException
|
||||
*/
|
||||
public static function sequenceAlreadyExists($name)
|
||||
{
|
||||
return SequenceAlreadyExists::new($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return SchemaException
|
||||
*/
|
||||
public static function sequenceDoesNotExist($name)
|
||||
{
|
||||
return SequenceDoesNotExist::new($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $constraintName
|
||||
* @param string $table
|
||||
*
|
||||
* @return SchemaException
|
||||
*/
|
||||
public static function uniqueConstraintDoesNotExist($constraintName, $table)
|
||||
{
|
||||
return UniqueConstraintDoesNotExist::new($constraintName, $table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $fkName
|
||||
* @param string $table
|
||||
*
|
||||
* @return SchemaException
|
||||
*/
|
||||
public static function foreignKeyDoesNotExist($fkName, $table)
|
||||
{
|
||||
return ForeignKeyDoesNotExist::new($fkName, $table);
|
||||
}
|
||||
|
||||
/** @return SchemaException */
|
||||
public static function namedForeignKeyRequired(Table $localTable, ForeignKeyConstraint $foreignKey)
|
||||
{
|
||||
return NamedForeignKeyRequired::new($localTable, $foreignKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $changeName
|
||||
*
|
||||
* @return SchemaException
|
||||
*/
|
||||
public static function alterTableChangeNotSupported($changeName)
|
||||
{
|
||||
return new self(
|
||||
sprintf("Alter table change not supported, given '%s'", $changeName),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
|
||||
/**
|
||||
* Creates a schema manager for the given connection.
|
||||
*
|
||||
* This interface is an extension point for applications that need to override schema managers.
|
||||
*/
|
||||
interface SchemaManagerFactory
|
||||
{
|
||||
public function createSchemaManager(Connection $connection): AbstractSchemaManager;
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Schema\Visitor\Visitor;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function count;
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Sequence structure.
|
||||
*/
|
||||
class Sequence extends AbstractAsset
|
||||
{
|
||||
/** @var int */
|
||||
protected $allocationSize = 1;
|
||||
|
||||
/** @var int */
|
||||
protected $initialValue = 1;
|
||||
|
||||
/** @var int|null */
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param int $allocationSize
|
||||
* @param int $initialValue
|
||||
* @param int|null $cache
|
||||
*/
|
||||
public function __construct($name, $allocationSize = 1, $initialValue = 1, $cache = null)
|
||||
{
|
||||
$this->_setName($name);
|
||||
$this->setAllocationSize($allocationSize);
|
||||
$this->setInitialValue($initialValue);
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/** @return int */
|
||||
public function getAllocationSize()
|
||||
{
|
||||
return $this->allocationSize;
|
||||
}
|
||||
|
||||
/** @return int */
|
||||
public function getInitialValue()
|
||||
{
|
||||
return $this->initialValue;
|
||||
}
|
||||
|
||||
/** @return int|null */
|
||||
public function getCache()
|
||||
{
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $allocationSize
|
||||
*
|
||||
* @return Sequence
|
||||
*/
|
||||
public function setAllocationSize($allocationSize)
|
||||
{
|
||||
if ($allocationSize > 0) {
|
||||
$this->allocationSize = $allocationSize;
|
||||
} else {
|
||||
$this->allocationSize = 1;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $initialValue
|
||||
*
|
||||
* @return Sequence
|
||||
*/
|
||||
public function setInitialValue($initialValue)
|
||||
{
|
||||
if ($initialValue > 0) {
|
||||
$this->initialValue = $initialValue;
|
||||
} else {
|
||||
$this->initialValue = 1;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $cache
|
||||
*
|
||||
* @return Sequence
|
||||
*/
|
||||
public function setCache($cache)
|
||||
{
|
||||
$this->cache = $cache;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this sequence is an autoincrement sequence for a given table.
|
||||
*
|
||||
* This is used inside the comparator to not report sequences as missing,
|
||||
* when the "from" schema implicitly creates the sequences.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isAutoIncrementsFor(Table $table)
|
||||
{
|
||||
$primaryKey = $table->getPrimaryKey();
|
||||
|
||||
if ($primaryKey === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pkColumns = $primaryKey->getColumns();
|
||||
|
||||
if (count($pkColumns) !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$column = $table->getColumn($pkColumns[0]);
|
||||
|
||||
if (! $column->getAutoincrement()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sequenceName = $this->getShortestName($table->getNamespaceName());
|
||||
$tableName = $table->getShortestName($table->getNamespaceName());
|
||||
$tableSequenceName = sprintf('%s_%s_seq', $tableName, $column->getShortestName($table->getNamespaceName()));
|
||||
|
||||
return $tableSequenceName === $sequenceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function visit(Visitor $visitor)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5435',
|
||||
'Sequence::visit() is deprecated.',
|
||||
);
|
||||
|
||||
$visitor->acceptSequence($this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,790 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\DriverManager;
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\DBAL\Platforms\SQLite;
|
||||
use Doctrine\DBAL\Platforms\SqlitePlatform;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\DBAL\Types\StringType;
|
||||
use Doctrine\DBAL\Types\TextType;
|
||||
use Doctrine\DBAL\Types\Type;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_change_key_case;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function file_exists;
|
||||
use function implode;
|
||||
use function preg_match;
|
||||
use function preg_match_all;
|
||||
use function preg_quote;
|
||||
use function preg_replace;
|
||||
use function rtrim;
|
||||
use function str_replace;
|
||||
use function strcasecmp;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function trim;
|
||||
use function unlink;
|
||||
use function usort;
|
||||
|
||||
use const CASE_LOWER;
|
||||
|
||||
/**
|
||||
* Sqlite SchemaManager.
|
||||
*
|
||||
* @extends AbstractSchemaManager<SqlitePlatform>
|
||||
*/
|
||||
class SqliteSchemaManager extends AbstractSchemaManager
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableNames()
|
||||
{
|
||||
return $this->doListTableNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTables()
|
||||
{
|
||||
return $this->doListTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see introspectTable()} instead.
|
||||
*/
|
||||
public function listTableDetails($name)
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5595',
|
||||
'%s is deprecated. Use introspectTable() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
return $this->doListTableDetails($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableColumns($table, $database = null)
|
||||
{
|
||||
return $this->doListTableColumns($table, $database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableIndexes($table)
|
||||
{
|
||||
return $this->doListTableIndexes($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function fetchForeignKeyColumnsByTable(string $databaseName): array
|
||||
{
|
||||
$columnsByTable = parent::fetchForeignKeyColumnsByTable($databaseName);
|
||||
|
||||
if (count($columnsByTable) > 0) {
|
||||
foreach ($columnsByTable as $table => $columns) {
|
||||
$columnsByTable[$table] = $this->addDetailsToTableForeignKeyColumns($table, $columns);
|
||||
}
|
||||
}
|
||||
|
||||
return $columnsByTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Delete the database file using the filesystem.
|
||||
*/
|
||||
public function dropDatabase($database)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/issues/4963',
|
||||
'SqliteSchemaManager::dropDatabase() is deprecated. Delete the database file using the filesystem.',
|
||||
);
|
||||
|
||||
if (! file_exists($database)) {
|
||||
return;
|
||||
}
|
||||
|
||||
unlink($database);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated The engine will create the database file automatically.
|
||||
*/
|
||||
public function createDatabase($database)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/issues/4963',
|
||||
'SqliteSchemaManager::createDatabase() is deprecated.'
|
||||
. ' The engine will create the database file automatically.',
|
||||
);
|
||||
|
||||
$params = $this->_conn->getParams();
|
||||
|
||||
$params['path'] = $database;
|
||||
unset($params['memory']);
|
||||
|
||||
$conn = DriverManager::getConnection($params);
|
||||
$conn->connect();
|
||||
$conn->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function createForeignKey(ForeignKeyConstraint $foreignKey, $table)
|
||||
{
|
||||
if (! $table instanceof Table) {
|
||||
$table = $this->listTableDetails($table);
|
||||
}
|
||||
|
||||
$this->alterTable(new TableDiff($table->getName(), [], [], [], [], [], [], $table, [$foreignKey]));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated Use {@see dropForeignKey()} and {@see createForeignKey()} instead.
|
||||
*/
|
||||
public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4897',
|
||||
'SqliteSchemaManager::dropAndCreateForeignKey() is deprecated.'
|
||||
. ' Use SqliteSchemaManager::dropForeignKey() and SqliteSchemaManager::createForeignKey() instead.',
|
||||
);
|
||||
|
||||
if (! $table instanceof Table) {
|
||||
$table = $this->listTableDetails($table);
|
||||
}
|
||||
|
||||
$this->alterTable(new TableDiff($table->getName(), [], [], [], [], [], [], $table, [], [$foreignKey]));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function dropForeignKey($foreignKey, $table)
|
||||
{
|
||||
if (! $table instanceof Table) {
|
||||
$table = $this->listTableDetails($table);
|
||||
}
|
||||
|
||||
$this->alterTable(new TableDiff($table->getName(), [], [], [], [], [], [], $table, [], [], [$foreignKey]));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function listTableForeignKeys($table, $database = null)
|
||||
{
|
||||
$table = $this->normalizeName($table);
|
||||
|
||||
$columns = $this->selectForeignKeyColumns('', $table)
|
||||
->fetchAllAssociative();
|
||||
|
||||
if (count($columns) > 0) {
|
||||
$columns = $this->addDetailsToTableForeignKeyColumns($table, $columns);
|
||||
}
|
||||
|
||||
return $this->_getPortableTableForeignKeysList($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableDefinition($table)
|
||||
{
|
||||
return $table['table_name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
|
||||
*/
|
||||
protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
|
||||
{
|
||||
$indexBuffer = [];
|
||||
|
||||
// fetch primary
|
||||
$indexArray = $this->_conn->fetchAllAssociative('SELECT * FROM PRAGMA_TABLE_INFO (?)', [$tableName]);
|
||||
|
||||
usort(
|
||||
$indexArray,
|
||||
/**
|
||||
* @param array<string,mixed> $a
|
||||
* @param array<string,mixed> $b
|
||||
*/
|
||||
static function (array $a, array $b): int {
|
||||
if ($a['pk'] === $b['pk']) {
|
||||
return $a['cid'] - $b['cid'];
|
||||
}
|
||||
|
||||
return $a['pk'] - $b['pk'];
|
||||
},
|
||||
);
|
||||
|
||||
foreach ($indexArray as $indexColumnRow) {
|
||||
if ($indexColumnRow['pk'] === 0 || $indexColumnRow['pk'] === '0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$indexBuffer[] = [
|
||||
'key_name' => 'primary',
|
||||
'primary' => true,
|
||||
'non_unique' => false,
|
||||
'column_name' => $indexColumnRow['name'],
|
||||
];
|
||||
}
|
||||
|
||||
// fetch regular indexes
|
||||
foreach ($tableIndexes as $tableIndex) {
|
||||
// Ignore indexes with reserved names, e.g. autoindexes
|
||||
if (strpos($tableIndex['name'], 'sqlite_') === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$keyName = $tableIndex['name'];
|
||||
$idx = [];
|
||||
$idx['key_name'] = $keyName;
|
||||
$idx['primary'] = false;
|
||||
$idx['non_unique'] = ! $tableIndex['unique'];
|
||||
|
||||
$indexArray = $this->_conn->fetchAllAssociative('SELECT * FROM PRAGMA_INDEX_INFO (?)', [$keyName]);
|
||||
|
||||
foreach ($indexArray as $indexColumnRow) {
|
||||
$idx['column_name'] = $indexColumnRow['name'];
|
||||
$indexBuffer[] = $idx;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableColumnList($table, $database, $tableColumns)
|
||||
{
|
||||
$list = parent::_getPortableTableColumnList($table, $database, $tableColumns);
|
||||
|
||||
// find column with autoincrement
|
||||
$autoincrementColumn = null;
|
||||
$autoincrementCount = 0;
|
||||
|
||||
foreach ($tableColumns as $tableColumn) {
|
||||
if ($tableColumn['pk'] === 0 || $tableColumn['pk'] === '0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$autoincrementCount++;
|
||||
if ($autoincrementColumn !== null || strtolower($tableColumn['type']) !== 'integer') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$autoincrementColumn = $tableColumn['name'];
|
||||
}
|
||||
|
||||
if ($autoincrementCount === 1 && $autoincrementColumn !== null) {
|
||||
foreach ($list as $column) {
|
||||
if ($autoincrementColumn !== $column->getName()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$column->setAutoincrement(true);
|
||||
}
|
||||
}
|
||||
|
||||
// inspect column collation and comments
|
||||
$createSql = $this->getCreateTableSQL($table);
|
||||
|
||||
foreach ($list as $columnName => $column) {
|
||||
$type = $column->getType();
|
||||
|
||||
if ($type instanceof StringType || $type instanceof TextType) {
|
||||
$column->setPlatformOption(
|
||||
'collation',
|
||||
$this->parseColumnCollationFromSQL($columnName, $createSql) ?? 'BINARY',
|
||||
);
|
||||
}
|
||||
|
||||
$comment = $this->parseColumnCommentFromSQL($columnName, $createSql);
|
||||
|
||||
if ($comment === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $this->extractDoctrineTypeFromComment($comment, '');
|
||||
|
||||
if ($type !== '') {
|
||||
$column->setType(Type::getType($type));
|
||||
|
||||
$comment = $this->removeDoctrineTypeFromComment($comment, $type);
|
||||
}
|
||||
|
||||
$column->setComment($comment);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableColumnDefinition($tableColumn)
|
||||
{
|
||||
$parts = explode('(', $tableColumn['type']);
|
||||
$tableColumn['type'] = trim($parts[0]);
|
||||
if (isset($parts[1])) {
|
||||
$length = trim($parts[1], ')');
|
||||
$tableColumn['length'] = $length;
|
||||
}
|
||||
|
||||
$dbType = strtolower($tableColumn['type']);
|
||||
$length = $tableColumn['length'] ?? null;
|
||||
$unsigned = false;
|
||||
|
||||
if (strpos($dbType, ' unsigned') !== false) {
|
||||
$dbType = str_replace(' unsigned', '', $dbType);
|
||||
$unsigned = true;
|
||||
}
|
||||
|
||||
$fixed = false;
|
||||
$type = $this->_platform->getDoctrineTypeMapping($dbType);
|
||||
$default = $tableColumn['dflt_value'];
|
||||
if ($default === 'NULL') {
|
||||
$default = null;
|
||||
}
|
||||
|
||||
if ($default !== null) {
|
||||
// SQLite returns the default value as a literal expression, so we need to parse it
|
||||
if (preg_match('/^\'(.*)\'$/s', $default, $matches) === 1) {
|
||||
$default = str_replace("''", "'", $matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
$notnull = (bool) $tableColumn['notnull'];
|
||||
|
||||
if (! isset($tableColumn['name'])) {
|
||||
$tableColumn['name'] = '';
|
||||
}
|
||||
|
||||
$precision = null;
|
||||
$scale = null;
|
||||
|
||||
switch ($dbType) {
|
||||
case 'char':
|
||||
$fixed = true;
|
||||
break;
|
||||
case 'float':
|
||||
case 'double':
|
||||
case 'real':
|
||||
case 'decimal':
|
||||
case 'numeric':
|
||||
if (isset($tableColumn['length'])) {
|
||||
if (strpos($tableColumn['length'], ',') === false) {
|
||||
$tableColumn['length'] .= ',0';
|
||||
}
|
||||
|
||||
[$precision, $scale] = array_map('trim', explode(',', $tableColumn['length']));
|
||||
}
|
||||
|
||||
$length = null;
|
||||
break;
|
||||
}
|
||||
|
||||
$options = [
|
||||
'length' => $length,
|
||||
'unsigned' => $unsigned,
|
||||
'fixed' => $fixed,
|
||||
'notnull' => $notnull,
|
||||
'default' => $default,
|
||||
'precision' => $precision,
|
||||
'scale' => $scale,
|
||||
];
|
||||
|
||||
return new Column($tableColumn['name'], Type::getType($type), $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableViewDefinition($view)
|
||||
{
|
||||
return new View($view['name'], $view['sql']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeysList($tableForeignKeys)
|
||||
{
|
||||
$list = [];
|
||||
foreach ($tableForeignKeys as $value) {
|
||||
$value = array_change_key_case($value, CASE_LOWER);
|
||||
$id = $value['id'];
|
||||
if (! isset($list[$id])) {
|
||||
if (! isset($value['on_delete']) || $value['on_delete'] === 'RESTRICT') {
|
||||
$value['on_delete'] = null;
|
||||
}
|
||||
|
||||
if (! isset($value['on_update']) || $value['on_update'] === 'RESTRICT') {
|
||||
$value['on_update'] = null;
|
||||
}
|
||||
|
||||
$list[$id] = [
|
||||
'name' => $value['constraint_name'],
|
||||
'local' => [],
|
||||
'foreign' => [],
|
||||
'foreignTable' => $value['table'],
|
||||
'onDelete' => $value['on_delete'],
|
||||
'onUpdate' => $value['on_update'],
|
||||
'deferrable' => $value['deferrable'],
|
||||
'deferred' => $value['deferred'],
|
||||
];
|
||||
}
|
||||
|
||||
$list[$id]['local'][] = $value['from'];
|
||||
|
||||
if ($value['to'] === null) {
|
||||
// Inferring a shorthand form for the foreign key constraint, where the "to" field is empty.
|
||||
// @see https://www.sqlite.org/foreignkeys.html#fk_indexes.
|
||||
$foreignTableIndexes = $this->_getPortableTableIndexesList([], $value['table']);
|
||||
|
||||
if (! isset($foreignTableIndexes['primary'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[$id]['foreign'] = [...$list[$id]['foreign'], ...$foreignTableIndexes['primary']->getColumns()];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$list[$id]['foreign'][] = $value['to'];
|
||||
}
|
||||
|
||||
return parent::_getPortableTableForeignKeysList($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function _getPortableTableForeignKeyDefinition($tableForeignKey): ForeignKeyConstraint
|
||||
{
|
||||
return new ForeignKeyConstraint(
|
||||
$tableForeignKey['local'],
|
||||
$tableForeignKey['foreignTable'],
|
||||
$tableForeignKey['foreign'],
|
||||
$tableForeignKey['name'],
|
||||
[
|
||||
'onDelete' => $tableForeignKey['onDelete'],
|
||||
'onUpdate' => $tableForeignKey['onUpdate'],
|
||||
'deferrable' => $tableForeignKey['deferrable'],
|
||||
'deferred' => $tableForeignKey['deferred'],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
private function parseColumnCollationFromSQL(string $column, string $sql): ?string
|
||||
{
|
||||
$pattern = '{(?:\W' . preg_quote($column) . '\W|\W'
|
||||
. preg_quote($this->_platform->quoteSingleIdentifier($column))
|
||||
. '\W)[^,(]+(?:\([^()]+\)[^,]*)?(?:(?:DEFAULT|CHECK)\s*(?:\(.*?\))?[^,]*)*COLLATE\s+["\']?([^\s,"\')]+)}is';
|
||||
|
||||
if (preg_match($pattern, $sql, $match) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $match[1];
|
||||
}
|
||||
|
||||
private function parseTableCommentFromSQL(string $table, string $sql): ?string
|
||||
{
|
||||
$pattern = '/\s* # Allow whitespace characters at start of line
|
||||
CREATE\sTABLE # Match "CREATE TABLE"
|
||||
(?:\W"' . preg_quote($this->_platform->quoteSingleIdentifier($table), '/') . '"\W|\W' . preg_quote($table, '/')
|
||||
. '\W) # Match table name (quoted and unquoted)
|
||||
( # Start capture
|
||||
(?:\s*--[^\n]*\n?)+ # Capture anything that starts with whitespaces followed by -- until the end of the line(s)
|
||||
)/ix';
|
||||
|
||||
if (preg_match($pattern, $sql, $match) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));
|
||||
|
||||
return $comment === '' ? null : $comment;
|
||||
}
|
||||
|
||||
private function parseColumnCommentFromSQL(string $column, string $sql): ?string
|
||||
{
|
||||
$pattern = '{[\s(,](?:\W' . preg_quote($this->_platform->quoteSingleIdentifier($column))
|
||||
. '\W|\W' . preg_quote($column) . '\W)(?:\([^)]*?\)|[^,(])*?,?((?:(?!\n))(?:\s*--[^\n]*\n?)+)}i';
|
||||
|
||||
if (preg_match($pattern, $sql, $match) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$comment = preg_replace('{^\s*--}m', '', rtrim($match[1], "\n"));
|
||||
|
||||
return $comment === '' ? null : $comment;
|
||||
}
|
||||
|
||||
/** @throws Exception */
|
||||
private function getCreateTableSQL(string $table): string
|
||||
{
|
||||
$sql = $this->_conn->fetchOne(
|
||||
<<<'SQL'
|
||||
SELECT sql
|
||||
FROM (
|
||||
SELECT *
|
||||
FROM sqlite_master
|
||||
UNION ALL
|
||||
SELECT *
|
||||
FROM sqlite_temp_master
|
||||
)
|
||||
WHERE type = 'table'
|
||||
AND name = ?
|
||||
SQL
|
||||
,
|
||||
[$table],
|
||||
);
|
||||
|
||||
if ($sql !== false) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<array<string,mixed>> $columns
|
||||
*
|
||||
* @return list<array<string,mixed>>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function addDetailsToTableForeignKeyColumns(string $table, array $columns): array
|
||||
{
|
||||
$foreignKeyDetails = $this->getForeignKeyDetails($table);
|
||||
$foreignKeyCount = count($foreignKeyDetails);
|
||||
|
||||
foreach ($columns as $i => $column) {
|
||||
// SQLite identifies foreign keys in reverse order of appearance in SQL
|
||||
$columns[$i] = array_merge($column, $foreignKeyDetails[$foreignKeyCount - $column['id'] - 1]);
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $table
|
||||
*
|
||||
* @return list<array<string, mixed>>
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getForeignKeyDetails($table)
|
||||
{
|
||||
$createSql = $this->getCreateTableSQL($table);
|
||||
|
||||
if (
|
||||
preg_match_all(
|
||||
'#
|
||||
(?:CONSTRAINT\s+(\S+)\s+)?
|
||||
(?:FOREIGN\s+KEY[^)]+\)\s*)?
|
||||
REFERENCES\s+\S+\s*(?:\([^)]+\))?
|
||||
(?:
|
||||
[^,]*?
|
||||
(NOT\s+DEFERRABLE|DEFERRABLE)
|
||||
(?:\s+INITIALLY\s+(DEFERRED|IMMEDIATE))?
|
||||
)?#isx',
|
||||
$createSql,
|
||||
$match,
|
||||
) === 0
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$names = $match[1];
|
||||
$deferrable = $match[2];
|
||||
$deferred = $match[3];
|
||||
$details = [];
|
||||
|
||||
for ($i = 0, $count = count($match[0]); $i < $count; $i++) {
|
||||
$details[] = [
|
||||
'constraint_name' => isset($names[$i]) && $names[$i] !== '' ? $names[$i] : null,
|
||||
'deferrable' => isset($deferrable[$i]) && strcasecmp($deferrable[$i], 'deferrable') === 0,
|
||||
'deferred' => isset($deferred[$i]) && strcasecmp($deferred[$i], 'deferred') === 0,
|
||||
];
|
||||
}
|
||||
|
||||
return $details;
|
||||
}
|
||||
|
||||
public function createComparator(): Comparator
|
||||
{
|
||||
return new SQLite\Comparator($this->_platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
public function getSchemaSearchPaths()
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/4821',
|
||||
'SqliteSchemaManager::getSchemaSearchPaths() is deprecated.',
|
||||
);
|
||||
|
||||
// SQLite does not support schemas or databases
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function selectTableNames(string $databaseName): Result
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT name AS table_name
|
||||
FROM sqlite_master
|
||||
WHERE type = 'table'
|
||||
AND name != 'sqlite_sequence'
|
||||
AND name != 'geometry_columns'
|
||||
AND name != 'spatial_ref_sys'
|
||||
UNION ALL
|
||||
SELECT name
|
||||
FROM sqlite_temp_master
|
||||
WHERE type = 'table'
|
||||
ORDER BY name
|
||||
SQL;
|
||||
|
||||
return $this->_conn->executeQuery($sql);
|
||||
}
|
||||
|
||||
protected function selectTableColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT t.name AS table_name,
|
||||
c.*
|
||||
FROM sqlite_master t
|
||||
JOIN pragma_table_info(t.name) c
|
||||
SQL;
|
||||
|
||||
$conditions = [
|
||||
"t.type = 'table'",
|
||||
"t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')",
|
||||
];
|
||||
$params = [];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 't.name = ?';
|
||||
$params[] = str_replace('.', '__', $tableName);
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, c.cid';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectIndexColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT t.name AS table_name,
|
||||
i.*
|
||||
FROM sqlite_master t
|
||||
JOIN pragma_index_list(t.name) i
|
||||
SQL;
|
||||
|
||||
$conditions = [
|
||||
"t.type = 'table'",
|
||||
"t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')",
|
||||
];
|
||||
$params = [];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 't.name = ?';
|
||||
$params[] = str_replace('.', '__', $tableName);
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, i.seq';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
protected function selectForeignKeyColumns(string $databaseName, ?string $tableName = null): Result
|
||||
{
|
||||
$sql = <<<'SQL'
|
||||
SELECT t.name AS table_name,
|
||||
p.*
|
||||
FROM sqlite_master t
|
||||
JOIN pragma_foreign_key_list(t.name) p
|
||||
ON p."seq" != "-1"
|
||||
SQL;
|
||||
|
||||
$conditions = [
|
||||
"t.type = 'table'",
|
||||
"t.name NOT IN ('geometry_columns', 'spatial_ref_sys', 'sqlite_sequence')",
|
||||
];
|
||||
$params = [];
|
||||
|
||||
if ($tableName !== null) {
|
||||
$conditions[] = 't.name = ?';
|
||||
$params[] = str_replace('.', '__', $tableName);
|
||||
}
|
||||
|
||||
$sql .= ' WHERE ' . implode(' AND ', $conditions) . ' ORDER BY t.name, p.id DESC, p.seq';
|
||||
|
||||
return $this->_conn->executeQuery($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function fetchTableOptionsByTable(string $databaseName, ?string $tableName = null): array
|
||||
{
|
||||
if ($tableName === null) {
|
||||
$tables = $this->listTableNames();
|
||||
} else {
|
||||
$tables = [$tableName];
|
||||
}
|
||||
|
||||
$tableOptions = [];
|
||||
foreach ($tables as $table) {
|
||||
$comment = $this->parseTableCommentFromSQL($table, $this->getCreateTableSQL($table));
|
||||
|
||||
if ($comment === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$tableOptions[$table]['comment'] = $comment;
|
||||
}
|
||||
|
||||
return $tableOptions;
|
||||
}
|
||||
}
|
||||
+1041
File diff suppressed because it is too large
Load Diff
+361
@@ -0,0 +1,361 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_filter;
|
||||
use function array_values;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Table Diff.
|
||||
*/
|
||||
class TableDiff
|
||||
{
|
||||
/**
|
||||
* @deprecated Use {@see getOldTable()} instead.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* @deprecated Rename tables via {@link AbstractSchemaManager::renameTable()} instead.
|
||||
*
|
||||
* @var string|false
|
||||
*/
|
||||
public $newName = false;
|
||||
|
||||
/**
|
||||
* All added columns
|
||||
*
|
||||
* @internal Use {@see getAddedColumns()} instead.
|
||||
*
|
||||
* @var Column[]
|
||||
*/
|
||||
public $addedColumns;
|
||||
|
||||
/**
|
||||
* All modified columns
|
||||
*
|
||||
* @internal Use {@see getModifiedColumns()} instead.
|
||||
*
|
||||
* @var ColumnDiff[]
|
||||
*/
|
||||
public $changedColumns = [];
|
||||
|
||||
/**
|
||||
* All dropped columns
|
||||
*
|
||||
* @internal Use {@see getDroppedColumns()} instead.
|
||||
*
|
||||
* @var Column[]
|
||||
*/
|
||||
public $removedColumns = [];
|
||||
|
||||
/**
|
||||
* Columns that are only renamed from key to column instance name.
|
||||
*
|
||||
* @internal Use {@see getRenamedColumns()} instead.
|
||||
*
|
||||
* @var Column[]
|
||||
*/
|
||||
public $renamedColumns = [];
|
||||
|
||||
/**
|
||||
* All added indexes.
|
||||
*
|
||||
* @internal Use {@see getAddedIndexes()} instead.
|
||||
*
|
||||
* @var Index[]
|
||||
*/
|
||||
public $addedIndexes = [];
|
||||
|
||||
/**
|
||||
* All changed indexes.
|
||||
*
|
||||
* @internal Use {@see getModifiedIndexes()} instead.
|
||||
*
|
||||
* @var Index[]
|
||||
*/
|
||||
public $changedIndexes = [];
|
||||
|
||||
/**
|
||||
* All removed indexes
|
||||
*
|
||||
* @internal Use {@see getDroppedIndexes()} instead.
|
||||
*
|
||||
* @var Index[]
|
||||
*/
|
||||
public $removedIndexes = [];
|
||||
|
||||
/**
|
||||
* Indexes that are only renamed but are identical otherwise.
|
||||
*
|
||||
* @internal Use {@see getRenamedIndexes()} instead.
|
||||
*
|
||||
* @var Index[]
|
||||
*/
|
||||
public $renamedIndexes = [];
|
||||
|
||||
/**
|
||||
* All added foreign key definitions
|
||||
*
|
||||
* @internal Use {@see getAddedForeignKeys()} instead.
|
||||
*
|
||||
* @var ForeignKeyConstraint[]
|
||||
*/
|
||||
public $addedForeignKeys = [];
|
||||
|
||||
/**
|
||||
* All changed foreign keys
|
||||
*
|
||||
* @internal Use {@see getModifiedForeignKeys()} instead.
|
||||
*
|
||||
* @var ForeignKeyConstraint[]
|
||||
*/
|
||||
public $changedForeignKeys = [];
|
||||
|
||||
/**
|
||||
* All removed foreign keys
|
||||
*
|
||||
* @internal Use {@see getDroppedForeignKeys()} instead.
|
||||
*
|
||||
* @var (ForeignKeyConstraint|string)[]
|
||||
*/
|
||||
public $removedForeignKeys = [];
|
||||
|
||||
/**
|
||||
* @internal Use {@see getOldTable()} instead.
|
||||
*
|
||||
* @var Table|null
|
||||
*/
|
||||
public $fromTable;
|
||||
|
||||
/**
|
||||
* Constructs a TableDiff object.
|
||||
*
|
||||
* @internal The diff can be only instantiated by a {@see Comparator}.
|
||||
*
|
||||
* @param string $tableName
|
||||
* @param array<Column> $addedColumns
|
||||
* @param array<ColumnDiff> $modifiedColumns
|
||||
* @param array<Column> $droppedColumns
|
||||
* @param array<Index> $addedIndexes
|
||||
* @param array<Index> $changedIndexes
|
||||
* @param array<Index> $removedIndexes
|
||||
* @param list<ForeignKeyConstraint> $addedForeignKeys
|
||||
* @param list<ForeignKeyConstraint> $changedForeignKeys
|
||||
* @param list<ForeignKeyConstraint|string> $removedForeignKeys
|
||||
* @param array<string,Column> $renamedColumns
|
||||
* @param array<string,Index> $renamedIndexes
|
||||
*/
|
||||
public function __construct(
|
||||
$tableName,
|
||||
$addedColumns = [],
|
||||
$modifiedColumns = [],
|
||||
$droppedColumns = [],
|
||||
$addedIndexes = [],
|
||||
$changedIndexes = [],
|
||||
$removedIndexes = [],
|
||||
?Table $fromTable = null,
|
||||
$addedForeignKeys = [],
|
||||
$changedForeignKeys = [],
|
||||
$removedForeignKeys = [],
|
||||
$renamedColumns = [],
|
||||
$renamedIndexes = []
|
||||
) {
|
||||
$this->name = $tableName;
|
||||
$this->addedColumns = $addedColumns;
|
||||
$this->changedColumns = $modifiedColumns;
|
||||
$this->renamedColumns = $renamedColumns;
|
||||
$this->removedColumns = $droppedColumns;
|
||||
$this->addedIndexes = $addedIndexes;
|
||||
$this->changedIndexes = $changedIndexes;
|
||||
$this->renamedIndexes = $renamedIndexes;
|
||||
$this->removedIndexes = $removedIndexes;
|
||||
$this->addedForeignKeys = $addedForeignKeys;
|
||||
$this->changedForeignKeys = $changedForeignKeys;
|
||||
$this->removedForeignKeys = $removedForeignKeys;
|
||||
|
||||
if ($fromTable === null) {
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5678',
|
||||
'Not passing the $fromTable to %s is deprecated.',
|
||||
__METHOD__,
|
||||
);
|
||||
}
|
||||
|
||||
$this->fromTable = $fromTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@see getOldTable()} instead.
|
||||
*
|
||||
* @param AbstractPlatform $platform The platform to use for retrieving this table diff's name.
|
||||
*
|
||||
* @return Identifier
|
||||
*/
|
||||
public function getName(AbstractPlatform $platform)
|
||||
{
|
||||
return new Identifier(
|
||||
$this->fromTable instanceof Table ? $this->fromTable->getQuotedName($platform) : $this->name,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Rename tables via {@link AbstractSchemaManager::renameTable()} instead.
|
||||
*
|
||||
* @return Identifier|false
|
||||
*/
|
||||
public function getNewName()
|
||||
{
|
||||
Deprecation::triggerIfCalledFromOutside(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5663',
|
||||
'%s is deprecated. Rename tables via AbstractSchemaManager::renameTable() instead.',
|
||||
__METHOD__,
|
||||
);
|
||||
|
||||
if ($this->newName === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return new Identifier($this->newName);
|
||||
}
|
||||
|
||||
public function getOldTable(): ?Table
|
||||
{
|
||||
return $this->fromTable;
|
||||
}
|
||||
|
||||
/** @return list<Column> */
|
||||
public function getAddedColumns(): array
|
||||
{
|
||||
return array_values($this->addedColumns);
|
||||
}
|
||||
|
||||
/** @return list<ColumnDiff> */
|
||||
public function getModifiedColumns(): array
|
||||
{
|
||||
return array_values($this->changedColumns);
|
||||
}
|
||||
|
||||
/** @return list<Column> */
|
||||
public function getDroppedColumns(): array
|
||||
{
|
||||
return array_values($this->removedColumns);
|
||||
}
|
||||
|
||||
/** @return array<string,Column> */
|
||||
public function getRenamedColumns(): array
|
||||
{
|
||||
return $this->renamedColumns;
|
||||
}
|
||||
|
||||
/** @return list<Index> */
|
||||
public function getAddedIndexes(): array
|
||||
{
|
||||
return array_values($this->addedIndexes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal This method exists only for compatibility with the current implementation of schema managers
|
||||
* that modify the diff while processing it.
|
||||
*/
|
||||
public function unsetAddedIndex(Index $index): void
|
||||
{
|
||||
$this->addedIndexes = array_filter(
|
||||
$this->addedIndexes,
|
||||
static function (Index $addedIndex) use ($index): bool {
|
||||
return $addedIndex !== $index;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array<Index> */
|
||||
public function getModifiedIndexes(): array
|
||||
{
|
||||
return array_values($this->changedIndexes);
|
||||
}
|
||||
|
||||
/** @return list<Index> */
|
||||
public function getDroppedIndexes(): array
|
||||
{
|
||||
return array_values($this->removedIndexes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal This method exists only for compatibility with the current implementation of schema managers
|
||||
* that modify the diff while processing it.
|
||||
*/
|
||||
public function unsetDroppedIndex(Index $index): void
|
||||
{
|
||||
$this->removedIndexes = array_filter(
|
||||
$this->removedIndexes,
|
||||
static function (Index $removedIndex) use ($index): bool {
|
||||
return $removedIndex !== $index;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** @return array<string,Index> */
|
||||
public function getRenamedIndexes(): array
|
||||
{
|
||||
return $this->renamedIndexes;
|
||||
}
|
||||
|
||||
/** @return list<ForeignKeyConstraint> */
|
||||
public function getAddedForeignKeys(): array
|
||||
{
|
||||
return $this->addedForeignKeys;
|
||||
}
|
||||
|
||||
/** @return list<ForeignKeyConstraint> */
|
||||
public function getModifiedForeignKeys(): array
|
||||
{
|
||||
return $this->changedForeignKeys;
|
||||
}
|
||||
|
||||
/** @return list<ForeignKeyConstraint|string> */
|
||||
public function getDroppedForeignKeys(): array
|
||||
{
|
||||
return $this->removedForeignKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal This method exists only for compatibility with the current implementation of the schema comparator.
|
||||
*
|
||||
* @param ForeignKeyConstraint|string $foreignKey
|
||||
*/
|
||||
public function unsetDroppedForeignKey($foreignKey): void
|
||||
{
|
||||
$this->removedForeignKeys = array_filter(
|
||||
$this->removedForeignKeys,
|
||||
static function ($removedForeignKey) use ($foreignKey): bool {
|
||||
return $removedForeignKey !== $foreignKey;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the diff is empty (contains no changes).
|
||||
*/
|
||||
public function isEmpty(): bool
|
||||
{
|
||||
return count($this->addedColumns) === 0
|
||||
&& count($this->changedColumns) === 0
|
||||
&& count($this->removedColumns) === 0
|
||||
&& count($this->renamedColumns) === 0
|
||||
&& count($this->addedIndexes) === 0
|
||||
&& count($this->changedIndexes) === 0
|
||||
&& count($this->removedIndexes) === 0
|
||||
&& count($this->renamedIndexes) === 0
|
||||
&& count($this->addedForeignKeys) === 0
|
||||
&& count($this->changedForeignKeys) === 0
|
||||
&& count($this->removedForeignKeys) === 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
|
||||
use function array_keys;
|
||||
use function array_map;
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
* Class for a unique constraint.
|
||||
*/
|
||||
class UniqueConstraint extends AbstractAsset implements Constraint
|
||||
{
|
||||
/**
|
||||
* Asset identifier instances of the column names the unique constraint is associated with.
|
||||
* array($columnName => Identifier)
|
||||
*
|
||||
* @var Identifier[]
|
||||
*/
|
||||
protected $columns = [];
|
||||
|
||||
/**
|
||||
* Platform specific flags.
|
||||
* array($flagName => true)
|
||||
*
|
||||
* @var true[]
|
||||
*/
|
||||
protected $flags = [];
|
||||
|
||||
/**
|
||||
* Platform specific options.
|
||||
*
|
||||
* @var mixed[]
|
||||
*/
|
||||
private array $options;
|
||||
|
||||
/**
|
||||
* @param string[] $columns
|
||||
* @param string[] $flags
|
||||
* @param mixed[] $options
|
||||
*/
|
||||
public function __construct(string $name, array $columns, array $flags = [], array $options = [])
|
||||
{
|
||||
$this->_setName($name);
|
||||
|
||||
$this->options = $options;
|
||||
|
||||
foreach ($columns as $column) {
|
||||
$this->addColumn($column);
|
||||
}
|
||||
|
||||
foreach ($flags as $flag) {
|
||||
$this->addFlag($flag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getColumns()
|
||||
{
|
||||
return array_keys($this->columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getQuotedColumns(AbstractPlatform $platform)
|
||||
{
|
||||
$columns = [];
|
||||
|
||||
foreach ($this->columns as $column) {
|
||||
$columns[] = $column->getQuotedName($platform);
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public function getUnquotedColumns(): array
|
||||
{
|
||||
return array_map([$this, 'trimQuotes'], $this->getColumns());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns platform specific flags for unique constraint.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getFlags(): array
|
||||
{
|
||||
return array_keys($this->flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds flag for a unique constraint that translates to platform specific handling.
|
||||
*
|
||||
* @return $this
|
||||
*
|
||||
* @example $uniqueConstraint->addFlag('CLUSTERED')
|
||||
*/
|
||||
public function addFlag(string $flag): UniqueConstraint
|
||||
{
|
||||
$this->flags[strtolower($flag)] = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this unique constraint have a specific flag?
|
||||
*/
|
||||
public function hasFlag(string $flag): bool
|
||||
{
|
||||
return isset($this->flags[strtolower($flag)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a flag.
|
||||
*/
|
||||
public function removeFlag(string $flag): void
|
||||
{
|
||||
unset($this->flags[strtolower($flag)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this unique constraint have a specific option?
|
||||
*/
|
||||
public function hasOption(string $name): bool
|
||||
{
|
||||
return isset($this->options[strtolower($name)]);
|
||||
}
|
||||
|
||||
/** @return mixed */
|
||||
public function getOption(string $name)
|
||||
{
|
||||
return $this->options[strtolower($name)];
|
||||
}
|
||||
|
||||
/** @return mixed[] */
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new column to the unique constraint.
|
||||
*/
|
||||
protected function addColumn(string $column): void
|
||||
{
|
||||
$this->columns[$column] = new Identifier($column);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema;
|
||||
|
||||
/**
|
||||
* Representation of a Database View.
|
||||
*/
|
||||
class View extends AbstractAsset
|
||||
{
|
||||
/** @var string */
|
||||
private $sql;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string $sql
|
||||
*/
|
||||
public function __construct($name, $sql)
|
||||
{
|
||||
$this->_setName($name);
|
||||
$this->sql = $sql;
|
||||
}
|
||||
|
||||
/** @return string */
|
||||
public function getSql()
|
||||
{
|
||||
return $this->sql;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Visitor;
|
||||
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\Sequence;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
|
||||
/**
|
||||
* Abstract Visitor with empty methods for easy extension.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class AbstractVisitor implements Visitor, NamespaceVisitor
|
||||
{
|
||||
public function acceptSchema(Schema $schema)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function acceptNamespace($namespaceName)
|
||||
{
|
||||
}
|
||||
|
||||
public function acceptTable(Table $table)
|
||||
{
|
||||
}
|
||||
|
||||
public function acceptColumn(Table $table, Column $column)
|
||||
{
|
||||
}
|
||||
|
||||
public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
|
||||
{
|
||||
}
|
||||
|
||||
public function acceptIndex(Table $table, Index $index)
|
||||
{
|
||||
}
|
||||
|
||||
public function acceptSequence(Sequence $sequence)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Visitor;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\Sequence;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
use function array_merge;
|
||||
|
||||
/** @deprecated Use {@link CreateSchemaObjectsSQLBuilder} instead. */
|
||||
class CreateSchemaSqlCollector extends AbstractVisitor
|
||||
{
|
||||
/** @var string[] */
|
||||
private array $createNamespaceQueries = [];
|
||||
|
||||
/** @var string[] */
|
||||
private array $createTableQueries = [];
|
||||
|
||||
/** @var string[] */
|
||||
private array $createSequenceQueries = [];
|
||||
|
||||
/** @var string[] */
|
||||
private array $createFkConstraintQueries = [];
|
||||
|
||||
private AbstractPlatform $platform;
|
||||
|
||||
public function __construct(AbstractPlatform $platform)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5416',
|
||||
'CreateSchemaSqlCollector is deprecated. Use CreateSchemaObjectsSQLBuilder instead.',
|
||||
);
|
||||
|
||||
$this->platform = $platform;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function acceptNamespace($namespaceName)
|
||||
{
|
||||
if (! $this->platform->supportsSchemas()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->createNamespaceQueries[] = $this->platform->getCreateSchemaSQL($namespaceName);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function acceptTable(Table $table)
|
||||
{
|
||||
$this->createTableQueries = array_merge($this->createTableQueries, $this->platform->getCreateTableSQL($table));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
|
||||
{
|
||||
if (! $this->platform->supportsForeignKeyConstraints()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->createFkConstraintQueries[] = $this->platform->getCreateForeignKeySQL($fkConstraint, $localTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function acceptSequence(Sequence $sequence)
|
||||
{
|
||||
$this->createSequenceQueries[] = $this->platform->getCreateSequenceSQL($sequence);
|
||||
}
|
||||
|
||||
/** @return void */
|
||||
public function resetQueries()
|
||||
{
|
||||
$this->createNamespaceQueries = [];
|
||||
$this->createTableQueries = [];
|
||||
$this->createSequenceQueries = [];
|
||||
$this->createFkConstraintQueries = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all queries collected so far.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getQueries()
|
||||
{
|
||||
return array_merge(
|
||||
$this->createNamespaceQueries,
|
||||
$this->createSequenceQueries,
|
||||
$this->createTableQueries,
|
||||
$this->createFkConstraintQueries,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Visitor;
|
||||
|
||||
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Schema\Sequence;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
use SplObjectStorage;
|
||||
|
||||
use function assert;
|
||||
use function strlen;
|
||||
|
||||
/**
|
||||
* Gathers SQL statements that allow to completely drop the current schema.
|
||||
*
|
||||
* @deprecated Use {@link DropSchemaObjectsSQLBuilder} instead.
|
||||
*/
|
||||
class DropSchemaSqlCollector extends AbstractVisitor
|
||||
{
|
||||
private SplObjectStorage $constraints;
|
||||
private SplObjectStorage $sequences;
|
||||
private SplObjectStorage $tables;
|
||||
private AbstractPlatform $platform;
|
||||
|
||||
public function __construct(AbstractPlatform $platform)
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5416',
|
||||
'DropSchemaSqlCollector is deprecated. Use DropSchemaObjectsSQLBuilder instead.',
|
||||
);
|
||||
|
||||
$this->platform = $platform;
|
||||
$this->initializeQueries();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function acceptTable(Table $table)
|
||||
{
|
||||
$this->tables->attach($table);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
|
||||
{
|
||||
if (strlen($fkConstraint->getName()) === 0) {
|
||||
throw SchemaException::namedForeignKeyRequired($localTable, $fkConstraint);
|
||||
}
|
||||
|
||||
$this->constraints->attach($fkConstraint, $localTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function acceptSequence(Sequence $sequence)
|
||||
{
|
||||
$this->sequences->attach($sequence);
|
||||
}
|
||||
|
||||
/** @return void */
|
||||
public function clearQueries()
|
||||
{
|
||||
$this->initializeQueries();
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public function getQueries()
|
||||
{
|
||||
$sql = [];
|
||||
|
||||
foreach ($this->constraints as $fkConstraint) {
|
||||
assert($fkConstraint instanceof ForeignKeyConstraint);
|
||||
$localTable = $this->constraints[$fkConstraint];
|
||||
$sql[] = $this->platform->getDropForeignKeySQL(
|
||||
$fkConstraint->getQuotedName($this->platform),
|
||||
$localTable->getQuotedName($this->platform),
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($this->sequences as $sequence) {
|
||||
assert($sequence instanceof Sequence);
|
||||
$sql[] = $this->platform->getDropSequenceSQL($sequence->getQuotedName($this->platform));
|
||||
}
|
||||
|
||||
foreach ($this->tables as $table) {
|
||||
assert($table instanceof Table);
|
||||
$sql[] = $this->platform->getDropTableSQL($table->getQuotedName($this->platform));
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
private function initializeQueries(): void
|
||||
{
|
||||
$this->constraints = new SplObjectStorage();
|
||||
$this->sequences = new SplObjectStorage();
|
||||
$this->tables = new SplObjectStorage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Visitor;
|
||||
|
||||
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
|
||||
use function current;
|
||||
use function file_put_contents;
|
||||
use function in_array;
|
||||
use function strtolower;
|
||||
|
||||
/**
|
||||
* Create a Graphviz output of a Schema.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
class Graphviz extends AbstractVisitor
|
||||
{
|
||||
private string $output = '';
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
|
||||
{
|
||||
$this->output .= $this->createNodeRelation(
|
||||
$fkConstraint->getLocalTableName() . ':col' . current($fkConstraint->getLocalColumns()) . ':se',
|
||||
$fkConstraint->getForeignTableName() . ':col' . current($fkConstraint->getForeignColumns()) . ':se',
|
||||
[
|
||||
'dir' => 'back',
|
||||
'arrowtail' => 'dot',
|
||||
'arrowhead' => 'normal',
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function acceptSchema(Schema $schema)
|
||||
{
|
||||
$this->output = 'digraph "' . $schema->getName() . '" {' . "\n";
|
||||
$this->output .= 'splines = true;' . "\n";
|
||||
$this->output .= 'overlap = false;' . "\n";
|
||||
$this->output .= 'outputorder=edgesfirst;' . "\n";
|
||||
$this->output .= 'mindist = 0.6;' . "\n";
|
||||
$this->output .= 'sep = .2;' . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function acceptTable(Table $table)
|
||||
{
|
||||
$this->output .= $this->createNode(
|
||||
$table->getName(),
|
||||
[
|
||||
'label' => $this->createTableLabel($table),
|
||||
'shape' => 'plaintext',
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
private function createTableLabel(Table $table): string
|
||||
{
|
||||
// Start the table
|
||||
$label = '<<TABLE CELLSPACING="0" BORDER="1" ALIGN="LEFT">';
|
||||
|
||||
// The title
|
||||
$label .= '<TR><TD BORDER="1" COLSPAN="3" ALIGN="CENTER" BGCOLOR="#fcaf3e">'
|
||||
. '<FONT COLOR="#2e3436" FACE="Helvetica" POINT-SIZE="12">' . $table->getName() . '</FONT></TD></TR>';
|
||||
|
||||
// The attributes block
|
||||
foreach ($table->getColumns() as $column) {
|
||||
$columnLabel = $column->getName();
|
||||
|
||||
$label .= '<TR>'
|
||||
. '<TD BORDER="0" ALIGN="LEFT" BGCOLOR="#eeeeec">'
|
||||
. '<FONT COLOR="#2e3436" FACE="Helvetica" POINT-SIZE="12">' . $columnLabel . '</FONT>'
|
||||
. '</TD>'
|
||||
. '<TD BORDER="0" ALIGN="LEFT" BGCOLOR="#eeeeec">'
|
||||
. '<FONT COLOR="#2e3436" FACE="Helvetica" POINT-SIZE="10">'
|
||||
. strtolower($column->getType()->getName())
|
||||
. '</FONT>'
|
||||
. '</TD>'
|
||||
. '<TD BORDER="0" ALIGN="RIGHT" BGCOLOR="#eeeeec" PORT="col' . $column->getName() . '">';
|
||||
|
||||
$primaryKey = $table->getPrimaryKey();
|
||||
|
||||
if ($primaryKey !== null && in_array($column->getName(), $primaryKey->getColumns(), true)) {
|
||||
$label .= "\xe2\x9c\xb7";
|
||||
}
|
||||
|
||||
$label .= '</TD></TR>';
|
||||
}
|
||||
|
||||
// End the table
|
||||
$label .= '</TABLE>>';
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
* @param string[] $options
|
||||
*/
|
||||
private function createNode($name, $options): string
|
||||
{
|
||||
$node = $name . ' [';
|
||||
foreach ($options as $key => $value) {
|
||||
$node .= $key . '=' . $value . ' ';
|
||||
}
|
||||
|
||||
$node .= "]\n";
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $node1
|
||||
* @param string $node2
|
||||
* @param string[] $options
|
||||
*/
|
||||
private function createNodeRelation($node1, $node2, $options): string
|
||||
{
|
||||
$relation = $node1 . ' -> ' . $node2 . ' [';
|
||||
foreach ($options as $key => $value) {
|
||||
$relation .= $key . '=' . $value . ' ';
|
||||
}
|
||||
|
||||
$relation .= "]\n";
|
||||
|
||||
return $relation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Graphviz Output
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOutput()
|
||||
{
|
||||
return $this->output . '}';
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes dot language output to a file. This should usually be a *.dot file.
|
||||
*
|
||||
* You have to convert the output into a viewable format. For example use "neato" on linux systems
|
||||
* and execute:
|
||||
*
|
||||
* neato -Tpng -o er.png er.dot
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function write($filename)
|
||||
{
|
||||
file_put_contents($filename, $this->getOutput());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Visitor;
|
||||
|
||||
/**
|
||||
* Visitor that can visit schema namespaces.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
interface NamespaceVisitor
|
||||
{
|
||||
/**
|
||||
* Accepts a schema namespace name.
|
||||
*
|
||||
* @param string $namespaceName The schema namespace name to accept.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function acceptNamespace($namespaceName);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Visitor;
|
||||
|
||||
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\Sequence;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
use Doctrine\Deprecations\Deprecation;
|
||||
|
||||
/**
|
||||
* Removes assets from a schema that are not in the default namespace.
|
||||
*
|
||||
* Some databases such as MySQL support cross databases joins, but don't
|
||||
* allow to call DDLs to a database from another connected database.
|
||||
* Before a schema is serialized into SQL this visitor can cleanup schemas with
|
||||
* non default namespaces.
|
||||
*
|
||||
* This visitor filters all these non-default namespaced tables and sequences
|
||||
* and removes them from the Schema instance.
|
||||
*
|
||||
* @deprecated Do not use namespaces if the target database platform doesn't support them.
|
||||
*/
|
||||
class RemoveNamespacedAssets extends AbstractVisitor
|
||||
{
|
||||
private ?Schema $schema = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
Deprecation::trigger(
|
||||
'doctrine/dbal',
|
||||
'https://github.com/doctrine/dbal/pull/5432',
|
||||
'RemoveNamespacedAssets is deprecated. Do not use namespaces'
|
||||
. " if the target database platform doesn't support them.",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function acceptSchema(Schema $schema)
|
||||
{
|
||||
$this->schema = $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function acceptTable(Table $table)
|
||||
{
|
||||
if ($this->schema === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($table->isInDefaultNamespace($this->schema->getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->schema->dropTable($table->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function acceptSequence(Sequence $sequence)
|
||||
{
|
||||
if ($this->schema === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($sequence->isInDefaultNamespace($this->schema->getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->schema->dropSequence($sequence->getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
|
||||
{
|
||||
if ($this->schema === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The table may already be deleted in a previous
|
||||
// RemoveNamespacedAssets#acceptTable call. Removing Foreign keys that
|
||||
// point to nowhere.
|
||||
if (! $this->schema->hasTable($fkConstraint->getForeignTableName())) {
|
||||
$localTable->removeForeignKey($fkConstraint->getName());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$foreignTable = $this->schema->getTable($fkConstraint->getForeignTableName());
|
||||
if ($foreignTable->isInDefaultNamespace($this->schema->getName())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$localTable->removeForeignKey($fkConstraint->getName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Doctrine\DBAL\Schema\Visitor;
|
||||
|
||||
use Doctrine\DBAL\Schema\Column;
|
||||
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\DBAL\Schema\SchemaException;
|
||||
use Doctrine\DBAL\Schema\Sequence;
|
||||
use Doctrine\DBAL\Schema\Table;
|
||||
|
||||
/**
|
||||
* Schema Visitor used for Validation or Generation purposes.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
interface Visitor
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function acceptSchema(Schema $schema);
|
||||
|
||||
/** @return void */
|
||||
public function acceptTable(Table $table);
|
||||
|
||||
/** @return void */
|
||||
public function acceptColumn(Table $table, Column $column);
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*
|
||||
* @throws SchemaException
|
||||
*/
|
||||
public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint);
|
||||
|
||||
/** @return void */
|
||||
public function acceptIndex(Table $table, Index $index);
|
||||
|
||||
/** @return void */
|
||||
public function acceptSequence(Sequence $sequence);
|
||||
}
|
||||
Reference in New Issue
Block a user