Laravel version update

Laravel version update
This commit is contained in:
Manish Verma
2018-08-06 18:48:58 +05:30
parent d143048413
commit 126fbb0255
13678 changed files with 1031482 additions and 778530 deletions

View File

@@ -0,0 +1,237 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Platforms\AbstractPlatform;
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
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
abstract class AbstractAsset
{
/**
* @var string
*/
protected $_name;
/**
* Namespace of the asset. If none isset the default namespace is assumed.
*
* @var string|null
*/
protected $_namespace = null;
/**
* @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 $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 lowerspaced. Lowerspacing 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.
*
* @param string $defaultNamespaceName
*
* @return string
*/
public function getFullQualifiedName($defaultNamespaceName)
{
$name = $this->getName();
if ( ! $this->_namespace) {
$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) {
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.
*
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
*
* @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 array $columnNames
* @param string $prefix
* @param int $maxSize
*
* @return string
*/
protected function _generateIdentifierName($columnNames, $prefix='', $maxSize=30)
{
$hash = implode("", array_map(function ($column) {
return dechex(crc32($column));
}, $columnNames));
return strtoupper(substr($prefix . '_' . $hash, 0, $maxSize));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,501 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Types\Type;
use const E_USER_DEPRECATED;
use function array_merge;
use function is_numeric;
use function method_exists;
use function sprintf;
use function trigger_error;
/**
* Object representation of a database column.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class Column extends AbstractAsset
{
/**
* @var Type
*/
protected $_type;
/**
* @var int|null
*/
protected $_length = null;
/**
* @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 string|null
*/
protected $_default = null;
/**
* @var bool
*/
protected $_autoincrement = false;
/**
* @var array
*/
protected $_platformOptions = [];
/**
* @var string|null
*/
protected $_columnDefinition = null;
/**
* @var string|null
*/
protected $_comment = null;
/**
* @var array
*/
protected $_customSchemaOptions = [];
/**
* Creates a new Column.
*
* @param string $columnName
* @param Type $type
* @param array $options
*/
public function __construct($columnName, Type $type, array $options=[])
{
$this->_setName($columnName);
$this->setType($type);
$this->setOptions($options);
}
/**
* @param array $options
*
* @return Column
*/
public function setOptions(array $options)
{
foreach ($options as $name => $value) {
$method = "set".$name;
if ( ! method_exists($this, $method)) {
// next major: throw an exception
@trigger_error(sprintf(
'The "%s" column option is not supported,'.
' setting it is deprecated and will cause an error in Doctrine 3.0',
$name
), E_USER_DEPRECATED);
continue;
}
$this->$method($value);
}
return $this;
}
/**
* @param Type $type
*
* @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 array $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 $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 string|null
*/
public function getDefault()
{
return $this->_default;
}
/**
* @return array
*/
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 $comment
*
* @return Column
*/
public function setComment($comment)
{
$this->_comment = $comment;
return $this;
}
/**
* @return string|null
*/
public function getComment()
{
return $this->_comment;
}
/**
* @param string $name
* @param mixed $value
*
* @return Column
*/
public function setCustomSchemaOption($name, $value)
{
$this->_customSchemaOptions[$name] = $value;
return $this;
}
/**
* @param string $name
*
* @return bool
*/
public function hasCustomSchemaOption($name)
{
return isset($this->_customSchemaOptions[$name]);
}
/**
* @param string $name
*
* @return mixed
*/
public function getCustomSchemaOption($name)
{
return $this->_customSchemaOptions[$name];
}
/**
* @param array $customSchemaOptions
*
* @return Column
*/
public function setCustomSchemaOptions(array $customSchemaOptions)
{
$this->_customSchemaOptions = $customSchemaOptions;
return $this;
}
/**
* @return array
*/
public function getCustomSchemaOptions()
{
return $this->_customSchemaOptions;
}
/**
* @return array
*/
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);
}
}

View File

@@ -0,0 +1,86 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use function in_array;
/**
* Represents the change of a column.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class ColumnDiff
{
/**
* @var string
*/
public $oldColumnName;
/**
* @var Column
*/
public $column;
/**
* @var array
*/
public $changedProperties = [];
/**
* @var Column
*/
public $fromColumn;
/**
* @param string $oldColumnName
* @param Column $column
* @param string[] $changedProperties
* @param Column $fromColumn
*/
public function __construct($oldColumnName, Column $column, array $changedProperties = [], Column $fromColumn = null)
{
$this->oldColumnName = $oldColumnName;
$this->column = $column;
$this->changedProperties = $changedProperties;
$this->fromColumn = $fromColumn;
}
/**
* @param string $propertyName
*
* @return bool
*/
public function hasChanged($propertyName)
{
return in_array($propertyName, $this->changedProperties);
}
/**
* @return Identifier
*/
public function getOldColumnName()
{
$quote = $this->fromColumn && $this->fromColumn->isQuoted();
return new Identifier($this->oldColumnName, $quote);
}
}

View File

@@ -0,0 +1,536 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Types;
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_shift;
use function array_unique;
use function count;
use function strtolower;
/**
* Compares two Schemas and return an instance of SchemaDiff.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class Comparator
{
/**
* @param \Doctrine\DBAL\Schema\Schema $fromSchema
* @param \Doctrine\DBAL\Schema\Schema $toSchema
*
* @return \Doctrine\DBAL\Schema\SchemaDiff
*/
public static function compareSchemas(Schema $fromSchema, Schema $toSchema)
{
$c = new self();
return $c->compare($fromSchema, $toSchema);
}
/**
* Returns a SchemaDiff object containing the differences between the schemas $fromSchema and $toSchema.
*
* The returned differences are returned in such a way that they contain the
* operations to change the schema stored in $fromSchema to the schema that is
* stored in $toSchema.
*
* @param \Doctrine\DBAL\Schema\Schema $fromSchema
* @param \Doctrine\DBAL\Schema\Schema $toSchema
*
* @return \Doctrine\DBAL\Schema\SchemaDiff
*/
public function compare(Schema $fromSchema, Schema $toSchema)
{
$diff = new SchemaDiff();
$diff->fromSchema = $fromSchema;
$foreignKeysToTable = [];
foreach ($toSchema->getNamespaces() as $namespace) {
if ( ! $fromSchema->hasNamespace($namespace)) {
$diff->newNamespaces[$namespace] = $namespace;
}
}
foreach ($fromSchema->getNamespaces() as $namespace) {
if ( ! $toSchema->hasNamespace($namespace)) {
$diff->removedNamespaces[$namespace] = $namespace;
}
}
foreach ($toSchema->getTables() as $table) {
$tableName = $table->getShortestName($toSchema->getName());
if ( ! $fromSchema->hasTable($tableName)) {
$diff->newTables[$tableName] = $toSchema->getTable($tableName);
} else {
$tableDifferences = $this->diffTable($fromSchema->getTable($tableName), $toSchema->getTable($tableName));
if ($tableDifferences !== false) {
$diff->changedTables[$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)) {
$diff->removedTables[$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 ($diff->removedTables as $tableName => $table) {
if (isset($foreignKeysToTable[$tableName])) {
$diff->orphanedForeignKeys = array_merge($diff->orphanedForeignKeys, $foreignKeysToTable[$tableName]);
// 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($diff->changedTables[$localTableName])) {
foreach ($diff->changedTables[$localTableName]->removedForeignKeys as $key => $removedForeignKey) {
// We check if the key is from the removed table if not we skip.
if ($tableName !== strtolower($removedForeignKey->getForeignTableName())) {
continue;
}
unset($diff->changedTables[$localTableName]->removedForeignKeys[$key]);
}
}
}
}
}
foreach ($toSchema->getSequences() as $sequence) {
$sequenceName = $sequence->getShortestName($toSchema->getName());
if ( ! $fromSchema->hasSequence($sequenceName)) {
if ( ! $this->isAutoIncrementSequenceInSchema($fromSchema, $sequence)) {
$diff->newSequences[] = $sequence;
}
} else {
if ($this->diffSequence($sequence, $fromSchema->getSequence($sequenceName))) {
$diff->changedSequences[] = $toSchema->getSequence($sequenceName);
}
}
}
foreach ($fromSchema->getSequences() as $sequence) {
if ($this->isAutoIncrementSequenceInSchema($toSchema, $sequence)) {
continue;
}
$sequenceName = $sequence->getShortestName($fromSchema->getName());
if ( ! $toSchema->hasSequence($sequenceName)) {
$diff->removedSequences[] = $sequence;
}
}
return $diff;
}
/**
* @param \Doctrine\DBAL\Schema\Schema $schema
* @param \Doctrine\DBAL\Schema\Sequence $sequence
*
* @return bool
*/
private function isAutoIncrementSequenceInSchema($schema, $sequence)
{
foreach ($schema->getTables() as $table) {
if ($sequence->isAutoIncrementsFor($table)) {
return true;
}
}
return false;
}
/**
* @param \Doctrine\DBAL\Schema\Sequence $sequence1
* @param \Doctrine\DBAL\Schema\Sequence $sequence2
*
* @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 $table1 and $table2.
*
* If there are no differences this method returns the boolean false.
*
* @param \Doctrine\DBAL\Schema\Table $table1
* @param \Doctrine\DBAL\Schema\Table $table2
*
* @return TableDiff|false
*/
public function diffTable(Table $table1, Table $table2)
{
$changes = 0;
$tableDifferences = new TableDiff($table1->getName());
$tableDifferences->fromTable = $table1;
$table1Columns = $table1->getColumns();
$table2Columns = $table2->getColumns();
/* See if all the fields in table 1 exist in table 2 */
foreach ($table2Columns as $columnName => $column) {
if ( !$table1->hasColumn($columnName)) {
$tableDifferences->addedColumns[$columnName] = $column;
$changes++;
}
}
/* See if there are any removed fields in table 2 */
foreach ($table1Columns as $columnName => $column) {
// See if column is removed in table 2.
if ( ! $table2->hasColumn($columnName)) {
$tableDifferences->removedColumns[$columnName] = $column;
$changes++;
continue;
}
// See if column has changed properties in table 2.
$changedProperties = $this->diffColumn($column, $table2->getColumn($columnName));
if ( ! empty($changedProperties)) {
$columnDiff = new ColumnDiff($column->getName(), $table2->getColumn($columnName), $changedProperties);
$columnDiff->fromColumn = $column;
$tableDifferences->changedColumns[$column->getName()] = $columnDiff;
$changes++;
}
}
$this->detectColumnRenamings($tableDifferences);
$table1Indexes = $table1->getIndexes();
$table2Indexes = $table2->getIndexes();
/* See if all the indexes in table 1 exist in table 2 */
foreach ($table2Indexes as $indexName => $index) {
if (($index->isPrimary() && $table1->hasPrimaryKey()) || $table1->hasIndex($indexName)) {
continue;
}
$tableDifferences->addedIndexes[$indexName] = $index;
$changes++;
}
/* See if there are any removed indexes in table 2 */
foreach ($table1Indexes as $indexName => $index) {
// See if index is removed in table 2.
if (($index->isPrimary() && ! $table2->hasPrimaryKey()) ||
! $index->isPrimary() && ! $table2->hasIndex($indexName)
) {
$tableDifferences->removedIndexes[$indexName] = $index;
$changes++;
continue;
}
// See if index has changed in table 2.
$table2Index = $index->isPrimary() ? $table2->getPrimaryKey() : $table2->getIndex($indexName);
if ($this->diffIndex($index, $table2Index)) {
$tableDifferences->changedIndexes[$indexName] = $table2Index;
$changes++;
}
}
$this->detectIndexRenamings($tableDifferences);
$fromFkeys = $table1->getForeignKeys();
$toFkeys = $table2->getForeignKeys();
foreach ($fromFkeys as $key1 => $constraint1) {
foreach ($toFkeys as $key2 => $constraint2) {
if ($this->diffForeignKey($constraint1, $constraint2) === false) {
unset($fromFkeys[$key1], $toFkeys[$key2]);
} else {
if (strtolower($constraint1->getName()) == strtolower($constraint2->getName())) {
$tableDifferences->changedForeignKeys[] = $constraint2;
$changes++;
unset($fromFkeys[$key1], $toFkeys[$key2]);
}
}
}
}
foreach ($fromFkeys as $constraint1) {
$tableDifferences->removedForeignKeys[] = $constraint1;
$changes++;
}
foreach ($toFkeys as $constraint2) {
$tableDifferences->addedForeignKeys[] = $constraint2;
$changes++;
}
return $changes ? $tableDifferences : false;
}
/**
* Try to find columns that only changed their name, rename operations maybe cheaper than add/drop
* however ambiguities between different possibilities should not lead to renaming at all.
*
* @return void
*/
private function detectColumnRenamings(TableDiff $tableDifferences)
{
$renameCandidates = [];
foreach ($tableDifferences->addedColumns as $addedColumnName => $addedColumn) {
foreach ($tableDifferences->removedColumns as $removedColumn) {
if (count($this->diffColumn($addedColumn, $removedColumn)) == 0) {
$renameCandidates[$addedColumn->getName()][] = [$removedColumn, $addedColumn, $addedColumnName];
}
}
}
foreach ($renameCandidates as $candidateColumns) {
if (count($candidateColumns) == 1) {
list($removedColumn, $addedColumn) = $candidateColumns[0];
$removedColumnName = strtolower($removedColumn->getName());
$addedColumnName = strtolower($addedColumn->getName());
if ( ! isset($tableDifferences->renamedColumns[$removedColumnName])) {
$tableDifferences->renamedColumns[$removedColumnName] = $addedColumn;
unset(
$tableDifferences->addedColumns[$addedColumnName],
$tableDifferences->removedColumns[$removedColumnName]
);
}
}
}
}
/**
* Try to find indexes that only changed their name, rename operations maybe cheaper than add/drop
* however ambiguities between different possibilities should not lead to renaming at all.
*
* @return void
*/
private function detectIndexRenamings(TableDiff $tableDifferences)
{
$renameCandidates = [];
// Gather possible rename candidates by comparing each added and removed index based on semantics.
foreach ($tableDifferences->addedIndexes as $addedIndexName => $addedIndex) {
foreach ($tableDifferences->removedIndexes as $removedIndex) {
if (! $this->diffIndex($addedIndex, $removedIndex)) {
$renameCandidates[$addedIndex->getName()][] = [$removedIndex, $addedIndex, $addedIndexName];
}
}
}
foreach ($renameCandidates as $candidateIndexes) {
// If the current rename candidate contains exactly one semantically equal index,
// we can safely rename it.
// Otherwise it is unclear if a rename action is really intended,
// therefore we let those ambiguous indexes be added/dropped.
if (count($candidateIndexes) === 1) {
list($removedIndex, $addedIndex) = $candidateIndexes[0];
$removedIndexName = strtolower($removedIndex->getName());
$addedIndexName = strtolower($addedIndex->getName());
if (! isset($tableDifferences->renamedIndexes[$removedIndexName])) {
$tableDifferences->renamedIndexes[$removedIndexName] = $addedIndex;
unset(
$tableDifferences->addedIndexes[$addedIndexName],
$tableDifferences->removedIndexes[$removedIndexName]
);
}
}
}
}
/**
* @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $key1
* @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $key2
*
* @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();
}
/**
* Returns the difference between the fields $field1 and $field2.
*
* If there are differences this method returns $field2, otherwise the
* boolean false.
*
* @param \Doctrine\DBAL\Schema\Column $column1
* @param \Doctrine\DBAL\Schema\Column $column2
*
* @return array
*/
public function diffColumn(Column $column1, Column $column2)
{
$properties1 = $column1->toArray();
$properties2 = $column2->toArray();
$changedProperties = [];
foreach (['type', 'notnull', 'unsigned', 'autoincrement'] as $property) {
if ($properties1[$property] != $properties2[$property]) {
$changedProperties[] = $property;
}
}
// This is a very nasty hack to make comparator work with the legacy json_array type, which should be killed in v3
if ($this->isALegacyJsonComparison($properties1['type'], $properties2['type'])) {
array_shift($changedProperties);
$changedProperties[] = 'comment';
}
if ($properties1['default'] != $properties2['default'] ||
// 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.
(null === $properties1['default'] && null !== $properties2['default']) ||
(null === $properties2['default'] && null !== $properties1['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'] &&
! (null === $properties1['comment'] && '' === $properties2['comment']) &&
! (null === $properties2['comment'] && '' === $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]) {
$changedProperties[] = $key;
}
}
return array_unique($changedProperties);
}
/**
* TODO: kill with fire on v3.0
*
* @deprecated
*/
private function isALegacyJsonComparison(Types\Type $one, Types\Type $other) : bool
{
if ( ! $one instanceof Types\JsonType || ! $other instanceof Types\JsonType) {
return false;
}
return ( ! $one instanceof Types\JsonArrayType && $other instanceof Types\JsonArrayType)
|| ( ! $other instanceof Types\JsonArrayType && $one instanceof Types\JsonArrayType);
}
/**
* Finds the difference between the indexes $index1 and $index2.
*
* Compares $index1 with $index2 and returns $index2 if there are any
* differences or false in case there are no differences.
*
* @param \Doctrine\DBAL\Schema\Index $index1
* @param \Doctrine\DBAL\Schema\Index $index2
*
* @return bool
*/
public function diffIndex(Index $index1, Index $index2)
{
return ! ($index1->isFullfilledBy($index2) && $index2->isFullfilledBy($index1));
}
}

View File

@@ -0,0 +1,66 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* Marker interface for constraints.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
interface Constraint
{
/**
* @return string
*/
public function getName();
/**
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
*
* @return string
*/
public function getQuotedName(AbstractPlatform $platform);
/**
* Returns the names of the referencing table columns
* the constraint is associated with.
*
* @return array
*/
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 \Doctrine\DBAL\Platforms\AbstractPlatform $platform The platform to use for quotation.
*
* @return array
*/
public function getQuotedColumns(AbstractPlatform $platform);
}

View File

@@ -0,0 +1,225 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use function array_change_key_case;
use function is_resource;
use function strpos;
use function strtolower;
use function substr;
use function trim;
/**
* IBM Db2 Schema Manager.
*
* @link www.doctrine-project.org
* @since 1.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class DB2SchemaManager extends AbstractSchemaManager
{
/**
* {@inheritdoc}
*
* Apparently creator is the schema not the user who created it:
* {@link http://publib.boulder.ibm.com/infocenter/dzichelp/v2r2/index.jsp?topic=/com.ibm.db29.doc.sqlref/db2z_sysibmsystablestable.htm}
*/
public function listTableNames()
{
$sql = $this->_platform->getListTablesSQL();
$sql .= " AND CREATOR = UPPER('".$this->_conn->getUsername()."')";
$tables = $this->_conn->fetchAll($sql);
return $this->filterAssetNames($this->_getPortableTablesList($tables));
}
/**
* {@inheritdoc}
*/
protected function _getPortableTableColumnDefinition($tableColumn)
{
$tableColumn = array_change_key_case($tableColumn, \CASE_LOWER);
$length = null;
$fixed = null;
$unsigned = false;
$scale = false;
$precision = false;
$default = null;
if (null !== $tableColumn['default'] && 'NULL' != $tableColumn['default']) {
$default = trim($tableColumn['default'], "'");
}
$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':
$length = $tableColumn['length'];
$fixed = false;
break;
case 'character':
$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,
'unsigned' => (bool) $unsigned,
'fixed' => (bool) $fixed,
'default' => $default,
'autoincrement' => (boolean) $tableColumn['autoincrement'],
'notnull' => (bool) ($tableColumn['nulls'] == 'N'),
'scale' => null,
'precision' => null,
'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== ''
? $tableColumn['comment']
: null,
'platformOptions' => [],
];
if ($scale !== null && $precision !== null) {
$options['scale'] = $scale;
$options['precision'] = $precision;
}
return new Column($tableColumn['colname'], \Doctrine\DBAL\Types\Type::getType($type), $options);
}
/**
* {@inheritdoc}
*/
protected function _getPortableTablesList($tables)
{
$tableNames = [];
foreach ($tables as $tableRow) {
$tableRow = array_change_key_case($tableRow, \CASE_LOWER);
$tableNames[] = $tableRow['name'];
}
return $tableNames;
}
/**
* {@inheritdoc}
*/
protected function _getPortableTableIndexesList($tableIndexRows, $tableName = null)
{
foreach ($tableIndexRows as &$tableIndexRow) {
$tableIndexRow = array_change_key_case($tableIndexRow, \CASE_LOWER);
$tableIndexRow['primary'] = (boolean) $tableIndexRow['primary'];
}
return parent::_getPortableTableIndexesList($tableIndexRows, $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);
}
/**
* {@inheritdoc}
*/
protected function _getPortableForeignKeyRuleDef($def)
{
if ($def == "C") {
return "CASCADE";
} elseif ($def == "N") {
return "SET NULL";
}
return null;
}
/**
* {@inheritdoc}
*/
protected function _getPortableViewDefinition($view)
{
$view = array_change_key_case($view, \CASE_LOWER);
// sadly this still segfaults on PDO_IBM, see http://pecl.php.net/bugs/bug.php?id=17199
//$view['text'] = (is_resource($view['text']) ? stream_get_contents($view['text']) : $view['text']);
if (!is_resource($view['text'])) {
$pos = strpos($view['text'], ' AS ');
$sql = substr($view['text'], $pos+4);
} else {
$sql = '';
}
return new View($view['name'], $sql);
}
}

View File

@@ -0,0 +1,122 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Types\Type;
use function explode;
use function strtolower;
use function trim;
/**
* Schema manager for the Drizzle RDBMS.
*
* @author Kim Hemsø Rasmussen <kimhemsoe@gmail.com>
*/
class DrizzleSchemaManager extends AbstractSchemaManager
{
/**
* {@inheritdoc}
*/
protected function _getPortableTableColumnDefinition($tableColumn)
{
$dbType = strtolower($tableColumn['DATA_TYPE']);
$type = $this->_platform->getDoctrineTypeMapping($dbType);
$type = $this->extractDoctrineTypeFromComment($tableColumn['COLUMN_COMMENT'], $type);
$tableColumn['COLUMN_COMMENT'] = $this->removeDoctrineTypeFromComment($tableColumn['COLUMN_COMMENT'], $type);
$options = [
'notnull' => !(bool) $tableColumn['IS_NULLABLE'],
'length' => (int) $tableColumn['CHARACTER_MAXIMUM_LENGTH'],
'default' => $tableColumn['COLUMN_DEFAULT'] ?? null,
'autoincrement' => (bool) $tableColumn['IS_AUTO_INCREMENT'],
'scale' => (int) $tableColumn['NUMERIC_SCALE'],
'precision' => (int) $tableColumn['NUMERIC_PRECISION'],
'comment' => isset($tableColumn['COLUMN_COMMENT']) && '' !== $tableColumn['COLUMN_COMMENT']
? $tableColumn['COLUMN_COMMENT']
: null,
];
$column = new Column($tableColumn['COLUMN_NAME'], Type::getType($type), $options);
if ( ! empty($tableColumn['COLLATION_NAME'])) {
$column->setPlatformOption('collation', $tableColumn['COLLATION_NAME']);
}
return $column;
}
/**
* {@inheritdoc}
*/
protected function _getPortableDatabaseDefinition($database)
{
return $database['SCHEMA_NAME'];
}
/**
* {@inheritdoc}
*/
protected function _getPortableTableDefinition($table)
{
return $table['TABLE_NAME'];
}
/**
* {@inheritdoc}
*/
public function _getPortableTableForeignKeyDefinition($tableForeignKey)
{
$columns = [];
foreach (explode(',', $tableForeignKey['CONSTRAINT_COLUMNS']) as $value) {
$columns[] = trim($value, ' `');
}
$refColumns = [];
foreach (explode(',', $tableForeignKey['REFERENCED_TABLE_COLUMNS']) as $value) {
$refColumns[] = trim($value, ' `');
}
return new ForeignKeyConstraint(
$columns,
$tableForeignKey['REFERENCED_TABLE_NAME'],
$refColumns,
$tableForeignKey['CONSTRAINT_NAME'],
[
'onUpdate' => $tableForeignKey['UPDATE_RULE'],
'onDelete' => $tableForeignKey['DELETE_RULE'],
]
);
}
/**
* {@inheritdoc}
*/
protected function _getPortableTableIndexesList($tableIndexes, $tableName = null)
{
$indexes = [];
foreach ($tableIndexes as $k) {
$k['primary'] = (boolean) $k['primary'];
$indexes[] = $k;
}
return parent::_getPortableTableIndexesList($indexes, $tableName);
}
}

View File

@@ -0,0 +1,397 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use function array_combine;
use function array_keys;
use function array_map;
use function end;
use function explode;
use function in_array;
use function strtolower;
use function strtoupper;
/**
* An abstraction class for a foreign key constraint.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @author Steve Müller <st.mueller@dzh-online.de>
* @link www.doctrine-project.org
* @since 2.0
*/
class ForeignKeyConstraint extends AbstractAsset implements Constraint
{
/**
* Instance of the referencing table the foreign key constraint is associated with.
*
* @var \Doctrine\DBAL\Schema\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;
/**
* @var array Options associated with the foreign key constraint.
*/
protected $_options;
/**
* Initializes the foreign key constraint.
*
* @param array $localColumnNames Names of the referencing table columns.
* @param Table|string $foreignTableName Referenced table.
* @param array $foreignColumnNames Names of the referenced table columns.
* @param string|null $name Name of the foreign key constraint.
* @param array $options Options associated with the foreign key constraint.
*/
public function __construct(array $localColumnNames, $foreignTableName, array $foreignColumnNames, $name = null, array $options = [])
{
$this->_setName($name);
$identifierConstructorCallback = function ($column) {
return new Identifier($column);
};
$this->_localColumnNames = $localColumnNames
? array_combine($localColumnNames, array_map($identifierConstructorCallback, $localColumnNames))
: [];
if ($foreignTableName instanceof Table) {
$this->_foreignTableName = $foreignTableName;
} else {
$this->_foreignTableName = new Identifier($foreignTableName);
}
$this->_foreignColumnNames = $foreignColumnNames
? array_combine($foreignColumnNames, array_map($identifierConstructorCallback, $foreignColumnNames))
: [];
$this->_options = $options;
}
/**
* Returns the name of the referencing table
* the foreign key constraint is associated with.
*
* @return string
*/
public function getLocalTableName()
{
return $this->_localTable->getName();
}
/**
* Sets the Table instance of the referencing table
* the foreign key constraint is associated with.
*
* @param \Doctrine\DBAL\Schema\Table $table Instance of the referencing table.
*
* @return void
*/
public function setLocalTable(Table $table)
{
$this->_localTable = $table;
}
/**
* @return Table
*/
public function getLocalTable()
{
return $this->_localTable;
}
/**
* Returns the names of the referencing table columns
* the foreign key constraint is associated with.
*
* @return array
*/
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 \Doctrine\DBAL\Platforms\AbstractPlatform $platform The platform to use for quotation.
*
* @return array
*/
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 array
*/
public function getUnquotedLocalColumns()
{
return array_map([$this, 'trimQuotes'], $this->getLocalColumns());
}
/**
* Returns unquoted representation of foreign table column names for comparison with other FK
*
* @return array
*/
public function getUnquotedForeignColumns()
{
return array_map([$this, 'trimQuotes'], $this->getForeignColumns());
}
/**
* {@inheritdoc}
*
* @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.
*
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform The platform to use for quotation.
*
* @see getQuotedLocalColumns
*
* @return array
*/
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()
{
$parts = explode(".", $this->_foreignTableName->getName());
return strtolower(end($parts));
}
/**
* 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 \Doctrine\DBAL\Platforms\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 array
*/
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 \Doctrine\DBAL\Platforms\AbstractPlatform $platform The platform to use for quotation.
*
* @return array
*/
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 array
*/
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.
*
* @return string|null
*/
private function onEvent($event)
{
if (isset($this->_options[$event])) {
$onEvent = strtoupper($this->_options[$event]);
if ( ! in_array($onEvent, ['NO ACTION', 'RESTRICT'])) {
return $onEvent;
}
}
return false;
}
/**
* 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;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
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.
*
* @author Steve Müller <st.mueller@dzh-online.de>
* @link www.doctrine-project.org
* @since 2.4
*/
class Identifier extends AbstractAsset
{
/**
* Constructor.
*
* @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) {
$this->_setName('"' . $this->getName() . '"');
}
}
}

View File

@@ -0,0 +1,352 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use function array_keys;
use function array_map;
use function array_search;
use function count;
use function is_string;
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 array
*/
protected $_flags = [];
/**
* Platform specific options
*
* @todo $_flags should eventually be refactored into options
*
* @var array
*/
private $options = [];
/**
* @param string $indexName
* @param string[] $columns
* @param bool $isUnique
* @param bool $isPrimary
* @param string[] $flags
* @param array $options
*/
public function __construct($indexName, array $columns, $isUnique = false, $isPrimary = false, array $flags = [], array $options = [])
{
$isUnique = $isUnique || $isPrimary;
$this->_setName($indexName);
$this->_isUnique = $isUnique;
$this->_isPrimary = $isPrimary;
$this->options = $options;
foreach ($columns as $column) {
$this->_addColumn($column);
}
foreach ($flags as $flag) {
$this->addFlag($flag);
}
}
/**
* @param string $column
*
* @return void
*
* @throws \InvalidArgumentException
*/
protected function _addColumn($column)
{
if (is_string($column)) {
$this->_columns[$column] = new Identifier($column);
} else {
throw new \InvalidArgumentException("Expecting a string as Index Column");
}
}
/**
* {@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()
{
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 $columnName
* @param int $pos
*
* @return bool
*/
public function hasColumnAtPosition($columnName, $pos = 0)
{
$columnName = $this->trimQuotes(strtolower($columnName));
$indexColumns = array_map('strtolower', $this->getUnquotedColumns());
return array_search($columnName, $indexColumns) === $pos;
}
/**
* Checks if this index exactly spans the given column names in the correct order.
*
* @param array $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]))) {
$sameColumns = false;
}
}
return $sameColumns;
}
/**
* Checks if the other index already fulfills all the indexing and constraint needs of the current one.
*
* @param Index $other
*
* @return bool
*/
public function isFullfilledBy(Index $other)
{
// 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->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.
*
* @param Index $other
*
* @return bool
*/
public function overrules(Index $other)
{
if ($other->isPrimary()) {
return false;
} elseif ($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.
*
* @example $index->addFlag('CLUSTERED')
*
* @param string $flag
*
* @return Index
*/
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 array
*/
public function getOptions()
{
return $this->options;
}
/**
* Return whether the two indexes have the same partial index
* @param \Doctrine\DBAL\Schema\Index $other
*
* @return bool
*/
private function samePartialIndex(Index $other)
{
if ($this->hasOption('where') && $other->hasOption('where') && $this->getOption('where') == $other->getOption('where')) {
return true;
}
return ! $this->hasOption('where') && ! $other->hasOption('where');
}
}

View File

@@ -0,0 +1,306 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Platforms\MariaDb1027Platform;
use Doctrine\DBAL\Platforms\MySqlPlatform;
use Doctrine\DBAL\Types\Type;
use const CASE_LOWER;
use function array_change_key_case;
use function array_shift;
use function array_values;
use function end;
use function preg_match;
use function preg_replace;
use function str_replace;
use function stripslashes;
use function strpos;
use function strtok;
use function strtolower;
/**
* Schema manager for the MySql RDBMS.
*
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
* @author Roman Borschel <roman@code-factory.org>
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0
*/
class MySqlSchemaManager extends AbstractSchemaManager
{
/**
* {@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 _getPortableUserDefinition($user)
{
return [
'user' => $user['User'],
'password' => $user['Password'],
];
}
/**
* {@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'];
}
$tableIndexes[$k] = $v;
}
return parent::_getPortableTableIndexesList($tableIndexes, $tableName);
}
/**
* {@inheritdoc}
*/
protected function _getPortableSequenceDefinition($sequence)
{
return end($sequence);
}
/**
* {@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, '(), ');
$length = $tableColumn['length'] ?? strtok('(), ');
$fixed = null;
if ( ! isset($tableColumn['name'])) {
$tableColumn['name'] = '';
}
$scale = null;
$precision = null;
$type = $this->_platform->getDoctrineTypeMapping($dbType);
// In cases where not connected to a database DESCRIBE $table does not return 'Comment'
if (isset($tableColumn['comment'])) {
$type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
$tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
}
switch ($dbType) {
case 'char':
case 'binary':
$fixed = true;
break;
case 'float':
case 'double':
case 'real':
case 'numeric':
case 'decimal':
if (preg_match('([A-Za-z]+\(([0-9]+)\,([0-9]+)\))', $tableColumn['type'], $match)) {
$precision = $match[1];
$scale = $match[2];
$length = null;
}
break;
case 'tinytext':
$length = MySqlPlatform::LENGTH_LIMIT_TINYTEXT;
break;
case 'text':
$length = MySqlPlatform::LENGTH_LIMIT_TEXT;
break;
case 'mediumtext':
$length = MySqlPlatform::LENGTH_LIMIT_MEDIUMTEXT;
break;
case 'tinyblob':
$length = MySqlPlatform::LENGTH_LIMIT_TINYBLOB;
break;
case 'blob':
$length = MySqlPlatform::LENGTH_LIMIT_BLOB;
break;
case 'mediumblob':
$length = MySqlPlatform::LENGTH_LIMIT_MEDIUMBLOB;
break;
case 'tinyint':
case 'smallint':
case 'mediumint':
case 'int':
case 'integer':
case 'bigint':
case 'year':
$length = null;
break;
}
if ($this->_platform instanceof MariaDb1027Platform) {
$columnDefault = $this->getMariaDb1027ColumnDefault($this->_platform, $tableColumn['default']);
} else {
$columnDefault = $tableColumn['default'];
}
$options = [
'length' => $length !== null ? (int) $length : null,
'unsigned' => strpos($tableColumn['type'], 'unsigned') !== false,
'fixed' => (bool) $fixed,
'default' => $columnDefault,
'notnull' => $tableColumn['null'] !== 'YES',
'scale' => null,
'precision' => null,
'autoincrement' => strpos($tableColumn['extra'], 'auto_increment') !== false,
'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== ''
? $tableColumn['comment']
: null,
];
if ($scale !== null && $precision !== null) {
$options['scale'] = (int) $scale;
$options['precision'] = (int) $precision;
}
$column = new Column($tableColumn['field'], Type::getType($type), $options);
if (isset($tableColumn['collation'])) {
$column->setPlatformOption('collation', $tableColumn['collation']);
}
return $column;
}
/**
* 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 null|string $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 ($columnDefault[0] === "'") {
return stripslashes(
str_replace("''", "'",
preg_replace('/^\'(.*)\'$/', '$1', $columnDefault)
)
);
}
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'];
}
$result = [];
foreach ($list as $constraint) {
$result[] = new ForeignKeyConstraint(
array_values($constraint['local']),
$constraint['foreignTable'],
array_values($constraint['foreign']),
$constraint['name'],
[
'onDelete' => $constraint['onDelete'],
'onUpdate' => $constraint['onUpdate'],
]
);
}
return $result;
}
}

View File

@@ -0,0 +1,437 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Platforms\OraclePlatform;
use Doctrine\DBAL\Types\Type;
use const CASE_LOWER;
use function array_change_key_case;
use function array_values;
use function assert;
use function is_null;
use function preg_match;
use function sprintf;
use function strpos;
use function strtolower;
use function strtoupper;
use function trim;
/**
* Oracle Schema Manager.
*
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0
*/
class OracleSchemaManager extends AbstractSchemaManager
{
/**
* {@inheritdoc}
*/
public function dropDatabase($database)
{
try {
parent::dropDatabase($database);
} catch (DBALException $exception) {
$exception = $exception->getPrevious();
if (! $exception instanceof DriverException) {
throw $exception;
}
// If we have a error code 1940 (ORA-01940), the drop database operation failed
// because of active connections on the database.
// To force dropping the database, we first have to close all active connections
// on that database and issue the drop database operation again.
if ($exception->getErrorCode() !== 1940) {
throw $exception;
}
$this->killUserSessions($database);
parent::dropDatabase($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 _getPortableUserDefinition($user)
{
$user = \array_change_key_case($user, CASE_LOWER);
return [
'user' => $user['username'],
];
}
/**
* {@inheritdoc}
*/
protected function _getPortableTableDefinition($table)
{
$table = \array_change_key_case($table, CASE_LOWER);
return $this->getQuotedIdentifierName($table['table_name']);
}
/**
* {@inheritdoc}
*
* @license New BSD License
* @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 (strtolower($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")) {
$dbType = "timestamptz";
} else {
$dbType = "timestamp";
}
}
$unsigned = $fixed = null;
if ( ! isset($tableColumn['column_name'])) {
$tableColumn['column_name'] = '';
}
// Default values returned from database sometimes have trailing spaces.
$tableColumn['data_default'] = trim($tableColumn['data_default']);
if ($tableColumn['data_default'] === '' || $tableColumn['data_default'] === 'NULL') {
$tableColumn['data_default'] = null;
}
if (null !== $tableColumn['data_default']) {
// Default values returned from database are enclosed in single quotes.
$tableColumn['data_default'] = trim($tableColumn['data_default'], "'");
}
$precision = null;
$scale = null;
$type = $this->_platform->getDoctrineTypeMapping($dbType);
$type = $this->extractDoctrineTypeFromComment($tableColumn['comments'], $type);
$tableColumn['comments'] = $this->removeDoctrineTypeFromComment($tableColumn['comments'], $type);
switch ($dbType) {
case 'number':
if ($tableColumn['data_precision'] == 20 && $tableColumn['data_scale'] == 0) {
$precision = 20;
$scale = 0;
$type = 'bigint';
} elseif ($tableColumn['data_precision'] == 5 && $tableColumn['data_scale'] == 0) {
$type = 'smallint';
$precision = 5;
$scale = 0;
} elseif ($tableColumn['data_precision'] == 1 && $tableColumn['data_scale'] == 0) {
$precision = 1;
$scale = 0;
$type = 'boolean';
} elseif ($tableColumn['data_scale'] > 0) {
$precision = $tableColumn['data_precision'];
$scale = $tableColumn['data_scale'];
$type = 'decimal';
}
$length = null;
break;
case 'pls_integer':
case 'binary_integer':
$length = null;
break;
case 'varchar':
case 'varchar2':
case 'nvarchar2':
$length = $tableColumn['char_length'];
$fixed = false;
break;
case 'char':
case 'nchar':
$length = $tableColumn['char_length'];
$fixed = true;
break;
case 'date':
case 'timestamp':
$length = null;
break;
case 'float':
case 'binary_float':
case 'binary_double':
$precision = $tableColumn['data_precision'];
$scale = $tableColumn['data_scale'];
$length = null;
break;
case 'clob':
case 'nclob':
$length = null;
break;
case 'blob':
case 'raw':
case 'long raw':
case 'bfile':
$length = null;
break;
case 'rowid':
case 'urowid':
default:
$length = null;
}
$options = [
'notnull' => (bool) ($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;
}
$result = [];
foreach ($list as $constraint) {
$result[] = new ForeignKeyConstraint(
array_values($constraint['local']), $this->getQuotedIdentifierName($constraint['foreignTable']),
array_values($constraint['foreign']), $this->getQuotedIdentifierName($constraint['name']),
['onDelete' => $constraint['onDelete']]
);
}
return $result;
}
/**
* {@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 _getPortableFunctionDefinition($function)
{
$function = \array_change_key_case($function, CASE_LOWER);
return $function['name'];
}
/**
* {@inheritdoc}
*/
protected function _getPortableDatabaseDefinition($database)
{
$database = \array_change_key_case($database, CASE_LOWER);
return $database['username'];
}
/**
* {@inheritdoc}
*/
public function createDatabase($database = null)
{
if ($database === null) {
$database = $this->_conn->getDatabase();
}
$params = $this->_conn->getParams();
$username = $database;
$password = $params['password'];
$query = 'CREATE USER ' . $username . ' IDENTIFIED BY ' . $password;
$this->_conn->executeUpdate($query);
$query = 'GRANT DBA TO ' . $username;
$this->_conn->executeUpdate($query);
}
/**
* @param string $table
*
* @return bool
*/
public function dropAutoincrement($table)
{
assert($this->_platform instanceof OraclePlatform);
$sql = $this->_platform->getDropAutoincrementSql($table);
foreach ($sql as $query) {
$this->_conn->executeUpdate($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.
*
* @return string The quoted identifier.
*/
private function getQuotedIdentifierName($identifier)
{
if (preg_match('/[a-z]/', $identifier)) {
return $this->_platform->quoteIdentifier($identifier);
}
return $identifier;
}
/**
* Kills sessions connected with the given user.
*
* This is useful to force DROP USER operations which could fail because of active user sessions.
*
* @param string $user The name of the user to kill sessions for.
*
* @return void
*/
private function killUserSessions($user)
{
$sql = <<<SQL
SELECT
s.sid,
s.serial#
FROM
gv\$session s,
gv\$process p
WHERE
s.username = ?
AND p.addr(+) = s.paddr
SQL;
$activeUserSessions = $this->_conn->fetchAll($sql, [strtoupper($user)]);
foreach ($activeUserSessions as $activeUserSession) {
$activeUserSession = array_change_key_case($activeUserSession, \CASE_LOWER);
$this->_execSql(
sprintf(
"ALTER SYSTEM KILL SESSION '%s, %s' IMMEDIATE",
$activeUserSession['sid'],
$activeUserSession['serial#']
)
);
}
}
}

View File

@@ -0,0 +1,498 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Exception\DriverException;
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
use Doctrine\DBAL\Types\Type;
use const CASE_LOWER;
use function array_change_key_case;
use function array_filter;
use function array_keys;
use function array_map;
use function array_shift;
use function assert;
use function explode;
use function in_array;
use function join;
use function preg_match;
use function preg_replace;
use function str_replace;
use function stripos;
use function strlen;
use function strpos;
use function strtolower;
use function trim;
/**
* PostgreSQL Schema Manager.
*
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.0
*/
class PostgreSqlSchemaManager extends AbstractSchemaManager
{
/**
* @var array
*/
private $existingSchemaPaths;
/**
* Gets all the existing schema names.
*
* @return array
*/
public function getSchemaNames()
{
$statement = $this->_conn->executeQuery("SELECT nspname FROM pg_namespace WHERE nspname !~ '^pg_.*' AND nspname != 'information_schema'");
return $statement->fetchAll(FetchMode::COLUMN);
}
/**
* Returns an array of schema search paths.
*
* This is a PostgreSQL only function.
*
* @return array
*/
public function getSchemaSearchPaths()
{
$params = $this->_conn->getParams();
$schema = explode(",", $this->_conn->fetchColumn('SHOW search_path'));
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.
*
* @return array
*/
public function getExistingSchemaSearchPaths()
{
if ($this->existingSchemaPaths === null) {
$this->determineExistingSchemaSearchPaths();
}
return $this->existingSchemaPaths;
}
/**
* Sets or resets the order of the existing schemas in the current search path of the user.
*
* This is a PostgreSQL only function.
*
* @return void
*/
public function determineExistingSchemaSearchPaths()
{
$names = $this->getSchemaNames();
$paths = $this->getSchemaSearchPaths();
$this->existingSchemaPaths = array_filter($paths, function ($v) use ($names) {
return in_array($v, $names);
});
}
/**
* {@inheritdoc}
*/
public function dropDatabase($database)
{
try {
parent::dropDatabase($database);
} catch (DriverException $exception) {
// If we have a SQLSTATE 55006, the drop database operation failed
// because of active connections on the database.
// To force dropping the database, we first have to close all active connections
// on that database and issue the drop database operation again.
if ($exception->getSQLState() !== '55006') {
throw $exception;
}
assert($this->_platform instanceof PostgreSqlPlatform);
$this->_execSql(
[
$this->_platform->getDisallowDatabaseConnectionsSQL($database),
$this->_platform->getCloseActiveDatabaseConnectionsSQL($database),
]
);
parent::dropDatabase($database);
}
}
/**
* {@inheritdoc}
*/
protected function _getPortableTableForeignKeyDefinition($tableForeignKey)
{
$onUpdate = null;
$onDelete = null;
$localColumns = null;
$foreignColumns = null;
$foreignTable = null;
if (preg_match('(ON UPDATE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) {
$onUpdate = $match[1];
}
if (preg_match('(ON DELETE ([a-zA-Z0-9]+( (NULL|ACTION|DEFAULT))?))', $tableForeignKey['condef'], $match)) {
$onDelete = $match[1];
}
if (preg_match('/FOREIGN KEY \((.+)\) REFERENCES (.+)\((.+)\)/', $tableForeignKey['condef'], $values)) {
// 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 _getPortableTriggerDefinition($trigger)
{
return $trigger['trigger_name'];
}
/**
* {@inheritdoc}
*/
protected function _getPortableViewDefinition($view)
{
return new View($view['schemaname'].'.'.$view['viewname'], $view['definition']);
}
/**
* {@inheritdoc}
*/
protected function _getPortableUserDefinition($user)
{
return [
'user' => $user['usename'],
'password' => $user['passwd']
];
}
/**
* {@inheritdoc}
*/
protected function _getPortableTableDefinition($table)
{
$schemas = $this->getExistingSchemaSearchPaths();
$firstSchema = array_shift($schemas);
if ($table['schema_name'] == $firstSchema) {
return $table['table_name'];
}
return $table['schema_name'] . "." . $table['table_name'];
}
/**
* {@inheritdoc}
*
* @license New BSD License
* @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
*/
protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
{
$buffer = [];
foreach ($tableIndexes as $row) {
$colNumbers = explode(' ', $row['indkey']);
$colNumbersSql = 'IN (' . join(' ,', $colNumbers) . ' )';
$columnNameSql = "SELECT attnum, attname FROM pg_attribute
WHERE attrelid={$row['indrelid']} AND attnum $colNumbersSql ORDER BY attnum ASC;";
$stmt = $this->_conn->executeQuery($columnNameSql);
$indexColumns = $stmt->fetchAll();
// required for getting the order of the columns right.
foreach ($colNumbers as $colNum) {
foreach ($indexColumns as $colRow) {
if ($colNum == $colRow['attnum']) {
$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}
*/
protected function _getPortableSequencesList($sequences)
{
$sequenceDefinitions = [];
foreach ($sequences as $sequence) {
if ($sequence['schemaname'] != 'public') {
$sequenceName = $sequence['schemaname'] . "." . $sequence['relname'];
} else {
$sequenceName = $sequence['relname'];
}
$sequenceDefinitions[$sequenceName] = $sequence;
}
$list = [];
foreach ($this->filterAssetNames(array_keys($sequenceDefinitions)) as $sequenceName) {
$list[] = $this->_getPortableSequenceDefinition($sequenceDefinitions[$sequenceName]);
}
return $list;
}
/**
* {@inheritdoc}
*/
protected function getPortableNamespaceDefinition(array $namespace)
{
return $namespace['nspname'];
}
/**
* {@inheritdoc}
*/
protected function _getPortableSequenceDefinition($sequence)
{
if ($sequence['schemaname'] !== 'public') {
$sequenceName = $sequence['schemaname'] . "." . $sequence['relname'];
} else {
$sequenceName = $sequence['relname'];
}
if ( ! isset($sequence['increment_by'], $sequence['min_value'])) {
/** @var string[] $data */
$data = $this->_conn->fetchAssoc('SELECT min_value, increment_by FROM ' . $this->_platform->quoteIdentifier($sequenceName));
$sequence += $data;
}
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 (preg_match("/^nextval\('(.*)'(::.*)?\)$/", $tableColumn['default'], $matches)) {
$tableColumn['sequence'] = $matches[1];
$tableColumn['default'] = null;
$autoincrement = true;
}
if (preg_match("/^['(](.*)[')]::.*$/", $tableColumn['default'], $matches)) {
$tableColumn['default'] = $matches[1];
}
if (stripos($tableColumn['default'], 'NULL') === 0) {
$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 (strlen($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':
$fixed = false;
break;
case 'varchar':
case 'interval':
case '_varchar':
$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)) {
$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'] && preg_match("('([^']+)'::)", $tableColumn['default'], $match)) {
$tableColumn['default'] = $match[1];
}
$options = [
'length' => $length,
'notnull' => (bool) $tableColumn['isnotnull'],
'default' => $tableColumn['default'],
'precision' => $precision,
'scale' => $scale,
'fixed' => $fixed,
'unsigned' => false,
'autoincrement' => $autoincrement,
'comment' => isset($tableColumn['comment']) && $tableColumn['comment'] !== ''
? $tableColumn['comment']
: null,
];
$column = new Column($tableColumn['field'], Type::getType($type), $options);
if (isset($tableColumn['collation']) && !empty($tableColumn['collation'])) {
$column->setPlatformOption('collation', $tableColumn['collation']);
}
if (in_array($column->getType()->getName(), [Type::JSON_ARRAY, Type::JSON], true)) {
$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 (strpos($defaultValue, '(') === 0) {
return trim($defaultValue, '()');
}
return $defaultValue;
}
}

View File

@@ -0,0 +1,249 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Platforms\SQLAnywherePlatform;
use Doctrine\DBAL\Types\Type;
use function assert;
use function preg_replace;
/**
* SAP Sybase SQL Anywhere schema manager.
*
* @author Steve Müller <st.mueller@dzh-online.de>
* @link www.doctrine-project.org
* @since 2.5
*/
class SQLAnywhereSchemaManager extends AbstractSchemaManager
{
/**
* {@inheritdoc}
*
* Starts a database after creation
* as SQL Anywhere needs a database to be started
* before it can be used.
*
* @see startDatabase
*/
public function createDatabase($database)
{
parent::createDatabase($database);
$this->startDatabase($database);
}
/**
* {@inheritdoc}
*
* Tries stopping a database before dropping
* as SQL Anywhere needs a database to be stopped
* before it can be dropped.
*
* @see stopDatabase
*/
public function dropDatabase($database)
{
$this->tryMethod('stopDatabase', $database);
parent::dropDatabase($database);
}
/**
* Starts a database.
*
* @param string $database The name of the database to start.
*/
public function startDatabase($database)
{
assert($this->_platform instanceof SQLAnywherePlatform);
$this->_execSql($this->_platform->getStartDatabaseSQL($database));
}
/**
* Stops a database.
*
* @param string $database The name of the database to stop.
*/
public function stopDatabase($database)
{
assert($this->_platform instanceof SQLAnywherePlatform);
$this->_execSql($this->_platform->getStopDatabaseSQL($database));
}
/**
* {@inheritdoc}
*/
protected function _getPortableDatabaseDefinition($database)
{
return $database['name'];
}
/**
* {@inheritdoc}
*/
protected function _getPortableSequenceDefinition($sequence)
{
return new Sequence($sequence['sequence_name'], $sequence['increment_by'], $sequence['start_with']);
}
/**
* {@inheritdoc}
*/
protected function _getPortableTableColumnDefinition($tableColumn)
{
$type = $this->_platform->getDoctrineTypeMapping($tableColumn['type']);
$type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
$tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
$precision = null;
$scale = null;
$fixed = false;
$default = null;
if (null !== $tableColumn['default']) {
// Strip quotes from default value.
$default = preg_replace(["/^'(.*)'$/", "/''/"], ["$1", "'"], $tableColumn['default']);
if ('autoincrement' == $default) {
$default = null;
}
}
switch ($tableColumn['type']) {
case 'binary':
case 'char':
case 'nchar':
$fixed = true;
}
switch ($type) {
case 'decimal':
case 'float':
$precision = $tableColumn['length'];
$scale = $tableColumn['scale'];
}
return new Column(
$tableColumn['column_name'],
Type::getType($type),
[
'length' => $type == 'string' ? $tableColumn['length'] : null,
'precision' => $precision,
'scale' => $scale,
'unsigned' => (bool) $tableColumn['unsigned'],
'fixed' => $fixed,
'notnull' => (bool) $tableColumn['notnull'],
'default' => $default,
'autoincrement' => (bool) $tableColumn['autoincrement'],
'comment' => isset($tableColumn['comment']) && '' !== $tableColumn['comment']
? $tableColumn['comment']
: null,
]
);
}
/**
* {@inheritdoc}
*/
protected function _getPortableTableDefinition($table)
{
return $table['table_name'];
}
/**
* {@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) {
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' => [
'notnull' => $tableForeignKey['notnull'],
'match' => $tableForeignKey['match'],
'onUpdate' => $tableForeignKey['on_update'],
'onDelete' => $tableForeignKey['on_delete'],
'check_on_commit' => $tableForeignKey['check_on_commit'],
'clustered' => $tableForeignKey['clustered'],
'for_olap_workload' => $tableForeignKey['for_olap_workload']
]
];
} else {
$foreignKeys[$tableForeignKey['index_name']]['local_columns'][] = $tableForeignKey['local_column'];
$foreignKeys[$tableForeignKey['index_name']]['foreign_columns'][] = $tableForeignKey['foreign_column'];
}
}
return parent::_getPortableTableForeignKeysList($foreignKeys);
}
/**
* {@inheritdoc}
*/
protected function _getPortableTableIndexesList($tableIndexRows, $tableName = null)
{
foreach ($tableIndexRows as &$tableIndex) {
$tableIndex['primary'] = (boolean) $tableIndex['primary'];
$tableIndex['flags'] = [];
if ($tableIndex['clustered']) {
$tableIndex['flags'][] = 'clustered';
}
if ($tableIndex['with_nulls_not_distinct']) {
$tableIndex['flags'][] = 'with_nulls_not_distinct';
}
if ($tableIndex['for_olap_workload']) {
$tableIndex['flags'][] = 'for_olap_workload';
}
}
return parent::_getPortableTableIndexesList($tableIndexRows, $tableName);
}
/**
* {@inheritdoc}
*/
protected function _getPortableViewDefinition($view)
{
return new View(
$view['table_name'],
preg_replace('/^.*\s+as\s+SELECT(.*)/i', "SELECT$1", $view['view_def'])
);
}
}

View File

@@ -0,0 +1,323 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\DriverException;
use Doctrine\DBAL\Types\Type;
use function count;
use function in_array;
use function preg_replace;
use function sprintf;
use function str_replace;
use function strpos;
use function strtok;
use function trim;
/**
* SQL Server Schema Manager.
*
* @license http://www.opensource.org/licenses/mit-license.php MIT
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
* @author Juozas Kaziukenas <juozas@juokaz.com>
* @author Steve Müller <st.mueller@dzh-online.de>
* @since 2.0
*/
class SQLServerSchemaManager extends AbstractSchemaManager
{
/**
* {@inheritdoc}
*/
public function dropDatabase($database)
{
try {
parent::dropDatabase($database);
} catch (DBALException $exception) {
$exception = $exception->getPrevious();
if (! $exception instanceof DriverException) {
throw $exception;
}
// If we have a error code 3702, the drop database operation failed
// because of active connections on the database.
// To force dropping the database, we first have to close all active connections
// on that database and issue the drop database operation again.
if ($exception->getErrorCode() !== 3702) {
throw $exception;
}
$this->closeActiveDatabaseConnections($database);
parent::dropDatabase($database);
}
}
/**
* {@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'], '(), ');
$fixed = null;
$length = (int) $tableColumn['length'];
$default = $tableColumn['default'];
if (!isset($tableColumn['name'])) {
$tableColumn['name'] = '';
}
while ($default != ($default2 = preg_replace("/^\((.*)\)$/", '$1', $default))) {
$default = trim($default2, "'");
if ($default == 'getdate()') {
$default = $this->_platform->getCurrentTimestampSQL();
}
}
switch ($dbType) {
case 'nchar':
case 'nvarchar':
case 'ntext':
// Unicode data requires 2 bytes per character
$length = $length / 2;
break;
case 'varchar':
// TEXT type is returned as VARCHAR(MAX) with a length of -1
if ($length == -1) {
$dbType = 'text';
}
break;
}
if ('char' === $dbType || 'nchar' === $dbType || 'binary' === $dbType) {
$fixed = true;
}
$type = $this->_platform->getDoctrineTypeMapping($dbType);
$type = $this->extractDoctrineTypeFromComment($tableColumn['comment'], $type);
$tableColumn['comment'] = $this->removeDoctrineTypeFromComment($tableColumn['comment'], $type);
$options = [
'length' => ($length == 0 || !in_array($type, ['text', 'string'])) ? null : $length,
'unsigned' => false,
'fixed' => (bool) $fixed,
'default' => $default !== 'NULL' ? $default : null,
'notnull' => (bool) $tableColumn['notnull'],
'scale' => $tableColumn['scale'],
'precision' => $tableColumn['precision'],
'autoincrement' => (bool) $tableColumn['autoincrement'],
'comment' => $tableColumn['comment'] !== '' ? $tableColumn['comment'] : null,
];
$column = new Column($tableColumn['name'], Type::getType($type), $options);
if (isset($tableColumn['collation']) && $tableColumn['collation'] !== 'NULL') {
$column->setPlatformOption('collation', $tableColumn['collation']);
}
return $column;
}
/**
* {@inheritdoc}
*/
protected function _getPortableTableForeignKeysList($tableForeignKeys)
{
$foreignKeys = [];
foreach ($tableForeignKeys as $tableForeignKey) {
if ( ! isset($foreignKeys[$tableForeignKey['ForeignKey']])) {
$foreignKeys[$tableForeignKey['ForeignKey']] = [
'local_columns' => [$tableForeignKey['ColumnName']],
'foreign_table' => $tableForeignKey['ReferenceTableName'],
'foreign_columns' => [$tableForeignKey['ReferenceColumnName']],
'name' => $tableForeignKey['ForeignKey'],
'options' => [
'onUpdate' => str_replace('_', ' ', $tableForeignKey['update_referential_action_desc']),
'onDelete' => str_replace('_', ' ', $tableForeignKey['delete_referential_action_desc'])
]
];
} else {
$foreignKeys[$tableForeignKey['ForeignKey']]['local_columns'][] = $tableForeignKey['ColumnName'];
$foreignKeys[$tableForeignKey['ForeignKey']]['foreign_columns'][] = $tableForeignKey['ReferenceColumnName'];
}
}
return parent::_getPortableTableForeignKeysList($foreignKeys);
}
/**
* {@inheritdoc}
*/
protected function _getPortableTableIndexesList($tableIndexRows, $tableName=null)
{
foreach ($tableIndexRows as &$tableIndex) {
$tableIndex['non_unique'] = (boolean) $tableIndex['non_unique'];
$tableIndex['primary'] = (boolean) $tableIndex['primary'];
$tableIndex['flags'] = $tableIndex['flags'] ? [$tableIndex['flags']] : null;
}
return parent::_getPortableTableIndexesList($tableIndexRows, $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 (isset($table['schema_name']) && $table['schema_name'] !== 'dbo') {
return $table['schema_name'] . '.' . $table['name'];
}
return $table['name'];
}
/**
* {@inheritdoc}
*/
protected function _getPortableDatabaseDefinition($database)
{
return $database['name'];
}
/**
* {@inheritdoc}
*/
protected function getPortableNamespaceDefinition(array $namespace)
{
return $namespace['name'];
}
/**
* {@inheritdoc}
*/
protected function _getPortableViewDefinition($view)
{
// @todo
return new View($view['name'], null);
}
/**
* {@inheritdoc}
*/
public function listTableIndexes($table)
{
$sql = $this->_platform->getListTableIndexesSQL($table, $this->_conn->getDatabase());
try {
$tableIndexes = $this->_conn->fetchAll($sql);
} catch (\PDOException $e) {
if ($e->getCode() == "IMSSP") {
return [];
} else {
throw $e;
}
} catch (DBALException $e) {
if (strpos($e->getMessage(), 'SQLSTATE [01000, 15472]') === 0) {
return [];
} else {
throw $e;
}
}
return $this->_getPortableTableIndexesList($tableIndexes, $table);
}
/**
* {@inheritdoc}
*/
public function alterTable(TableDiff $tableDiff)
{
if (count($tableDiff->removedColumns) > 0) {
foreach ($tableDiff->removedColumns as $col) {
$columnConstraintSql = $this->getColumnConstraintSQL($tableDiff->name, $col->getName());
foreach ($this->_conn->fetchAll($columnConstraintSql) as $constraint) {
$this->_conn->exec("ALTER TABLE $tableDiff->name DROP CONSTRAINT " . $constraint['Name']);
}
}
}
parent::alterTable($tableDiff);
}
/**
* Returns the SQL to retrieve the constraints for a given column.
*
* @param string $table
* @param string $column
*
* @return string
*/
private function getColumnConstraintSQL($table, $column)
{
return "SELECT SysObjects.[Name]
FROM SysObjects INNER JOIN (SELECT [Name],[ID] FROM SysObjects WHERE XType = 'U') AS Tab
ON Tab.[ID] = Sysobjects.[Parent_Obj]
INNER JOIN sys.default_constraints DefCons ON DefCons.[object_id] = Sysobjects.[ID]
INNER JOIN SysColumns Col ON Col.[ColID] = DefCons.[parent_column_id] AND Col.[ID] = Tab.[ID]
WHERE Col.[Name] = " . $this->_conn->quote($column) ." AND Tab.[Name] = " . $this->_conn->quote($table) . "
ORDER BY Col.[Name]";
}
/**
* Closes currently active connections on the given database.
*
* This is useful to force DROP DATABASE operations which could fail because of active connections.
*
* @param string $database The name of the database to close currently active connections for.
*
* @return void
*/
private function closeActiveDatabaseConnections($database)
{
$database = new Identifier($database);
$this->_execSql(
sprintf(
'ALTER DATABASE %s SET SINGLE_USER WITH ROLLBACK IMMEDIATE',
$database->getQuotedName($this->_platform)
)
);
}
}

View File

@@ -0,0 +1,515 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Schema\Visitor\CreateSchemaSqlCollector;
use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector;
use Doctrine\DBAL\Schema\Visitor\NamespaceVisitor;
use Doctrine\DBAL\Schema\Visitor\Visitor;
use Doctrine\DBAL\Platforms\AbstractPlatform;
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.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class Schema extends AbstractAsset
{
/**
* The namespaces in this schema.
*
* @var array
*/
private $namespaces = [];
/**
* @var \Doctrine\DBAL\Schema\Table[]
*/
protected $_tables = [];
/**
* @var \Doctrine\DBAL\Schema\Sequence[]
*/
protected $_sequences = [];
/**
* @var SchemaConfig
*/
protected $_schemaConfig = false;
/**
* @param \Doctrine\DBAL\Schema\Table[] $tables
* @param \Doctrine\DBAL\Schema\Sequence[] $sequences
* @param \Doctrine\DBAL\Schema\SchemaConfig $schemaConfig
* @param array $namespaces
*/
public function __construct(
array $tables = [],
array $sequences = [],
SchemaConfig $schemaConfig = null,
array $namespaces = []
) {
if ($schemaConfig == null) {
$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);
}
}
/**
* @return bool
*/
public function hasExplicitForeignKeyIndexes()
{
return $this->_schemaConfig->hasExplicitForeignKeyIndexes();
}
/**
* @param \Doctrine\DBAL\Schema\Table $table
*
* @return void
*
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
protected function _addTable(Table $table)
{
$namespaceName = $table->getNamespaceName();
$tableName = $table->getFullQualifiedName($this->getName());
if (isset($this->_tables[$tableName])) {
throw SchemaException::tableAlreadyExists($tableName);
}
if ( ! $table->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName)) {
$this->createNamespace($namespaceName);
}
$this->_tables[$tableName] = $table;
$table->setSchemaConfig($this->_schemaConfig);
}
/**
* @param \Doctrine\DBAL\Schema\Sequence $sequence
*
* @return void
*
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
protected function _addSequence(Sequence $sequence)
{
$namespaceName = $sequence->getNamespaceName();
$seqName = $sequence->getFullQualifiedName($this->getName());
if (isset($this->_sequences[$seqName])) {
throw SchemaException::sequenceAlreadyExists($seqName);
}
if ( ! $sequence->isInDefaultNamespace($this->getName()) && ! $this->hasNamespace($namespaceName)) {
$this->createNamespace($namespaceName);
}
$this->_sequences[$seqName] = $sequence;
}
/**
* Returns the namespaces of this schema.
*
* @return array A list of namespace names.
*/
public function getNamespaces()
{
return $this->namespaces;
}
/**
* Gets all tables of this schema.
*
* @return \Doctrine\DBAL\Schema\Table[]
*/
public function getTables()
{
return $this->_tables;
}
/**
* @param string $tableName
*
* @return \Doctrine\DBAL\Schema\Table
*
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
public function getTable($tableName)
{
$tableName = $this->getFullQualifiedAssetName($tableName);
if (!isset($this->_tables[$tableName])) {
throw SchemaException::tableDoesNotExist($tableName);
}
return $this->_tables[$tableName];
}
/**
* @param string $name
*
* @return string
*/
private function getFullQualifiedAssetName($name)
{
$name = $this->getUnquotedAssetName($name);
if (strpos($name, ".") === false) {
$name = $this->getName() . "." . $name;
}
return strtolower($name);
}
/**
* Returns the unquoted representation of a given asset name.
*
* @param string $assetName Quoted or unquoted representation of an asset name.
*
* @return string
*/
private function getUnquotedAssetName($assetName)
{
if ($this->isIdentifierQuoted($assetName)) {
return $this->trimQuotes($assetName);
}
return $assetName;
}
/**
* Does this schema have a namespace with the given name?
*
* @param string $namespaceName
*
* @return bool
*/
public function hasNamespace($namespaceName)
{
$namespaceName = strtolower($this->getUnquotedAssetName($namespaceName));
return isset($this->namespaces[$namespaceName]);
}
/**
* Does this schema have a table with the given name?
*
* @param string $tableName
*
* @return bool
*/
public function hasTable($tableName)
{
$tableName = $this->getFullQualifiedAssetName($tableName);
return isset($this->_tables[$tableName]);
}
/**
* Gets all table names, prefixed with a schema name, even the default one if present.
*
* @return array
*/
public function getTableNames()
{
return array_keys($this->_tables);
}
/**
* @param string $sequenceName
*
* @return bool
*/
public function hasSequence($sequenceName)
{
$sequenceName = $this->getFullQualifiedAssetName($sequenceName);
return isset($this->_sequences[$sequenceName]);
}
/**
* @param string $sequenceName
*
* @return \Doctrine\DBAL\Schema\Sequence
*
* @throws \Doctrine\DBAL\Schema\SchemaException
*/
public function getSequence($sequenceName)
{
$sequenceName = $this->getFullQualifiedAssetName($sequenceName);
if (!$this->hasSequence($sequenceName)) {
throw SchemaException::sequenceDoesNotExist($sequenceName);
}
return $this->_sequences[$sequenceName];
}
/**
* @return \Doctrine\DBAL\Schema\Sequence[]
*/
public function getSequences()
{
return $this->_sequences;
}
/**
* Creates a new namespace.
*
* @param string $namespaceName The name of the namespace to create.
*
* @return \Doctrine\DBAL\Schema\Schema This schema instance.
*
* @throws SchemaException
*/
public function createNamespace($namespaceName)
{
$unquotedNamespaceName = strtolower($this->getUnquotedAssetName($namespaceName));
if (isset($this->namespaces[$unquotedNamespaceName])) {
throw SchemaException::namespaceAlreadyExists($unquotedNamespaceName);
}
$this->namespaces[$unquotedNamespaceName] = $namespaceName;
return $this;
}
/**
* Creates a new table.
*
* @param string $tableName
*
* @return \Doctrine\DBAL\Schema\Table
*/
public function createTable($tableName)
{
$table = new Table($tableName);
$this->_addTable($table);
foreach ($this->_schemaConfig->getDefaultTableOptions() as $name => $value) {
$table->addOption($name, $value);
}
return $table;
}
/**
* Renames a table.
*
* @param string $oldTableName
* @param string $newTableName
*
* @return \Doctrine\DBAL\Schema\Schema
*/
public function renameTable($oldTableName, $newTableName)
{
$table = $this->getTable($oldTableName);
$table->_setName($newTableName);
$this->dropTable($oldTableName);
$this->_addTable($table);
return $this;
}
/**
* Drops a table from the schema.
*
* @param string $tableName
*
* @return \Doctrine\DBAL\Schema\Schema
*/
public function dropTable($tableName)
{
$tableName = $this->getFullQualifiedAssetName($tableName);
$this->getTable($tableName);
unset($this->_tables[$tableName]);
return $this;
}
/**
* Creates a new sequence.
*
* @param string $sequenceName
* @param int $allocationSize
* @param int $initialValue
*
* @return \Doctrine\DBAL\Schema\Sequence
*/
public function createSequence($sequenceName, $allocationSize=1, $initialValue=1)
{
$seq = new Sequence($sequenceName, $allocationSize, $initialValue);
$this->_addSequence($seq);
return $seq;
}
/**
* @param string $sequenceName
*
* @return \Doctrine\DBAL\Schema\Schema
*/
public function dropSequence($sequenceName)
{
$sequenceName = $this->getFullQualifiedAssetName($sequenceName);
unset($this->_sequences[$sequenceName]);
return $this;
}
/**
* Returns an array of necessary SQL queries to create the schema on the given platform.
*
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
*
* @return array
*/
public function toSql(AbstractPlatform $platform)
{
$sqlCollector = new CreateSchemaSqlCollector($platform);
$this->visit($sqlCollector);
return $sqlCollector->getQueries();
}
/**
* Return an array of necessary SQL queries to drop the schema on the given platform.
*
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
*
* @return array
*/
public function toDropSql(AbstractPlatform $platform)
{
$dropSqlCollector = new DropSchemaSqlCollector($platform);
$this->visit($dropSqlCollector);
return $dropSqlCollector->getQueries();
}
/**
* @param \Doctrine\DBAL\Schema\Schema $toSchema
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
*
* @return array
*/
public function getMigrateToSql(Schema $toSchema, AbstractPlatform $platform)
{
$comparator = new Comparator();
$schemaDiff = $comparator->compare($this, $toSchema);
return $schemaDiff->toSql($platform);
}
/**
* @param \Doctrine\DBAL\Schema\Schema $fromSchema
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
*
* @return array
*/
public function getMigrateFromSql(Schema $fromSchema, AbstractPlatform $platform)
{
$comparator = new Comparator();
$schemaDiff = $comparator->compare($fromSchema, $this);
return $schemaDiff->toSql($platform);
}
/**
* @param \Doctrine\DBAL\Schema\Visitor\Visitor $visitor
*
* @return void
*/
public function visit(Visitor $visitor)
{
$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;
}
}
}

View File

@@ -0,0 +1,129 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
/**
* Configuration for a Schema.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class SchemaConfig
{
/**
* @var bool
*/
protected $hasExplicitForeignKeyIndexes = false;
/**
* @var int
*/
protected $maxIdentifierLength = 63;
/**
* @var string
*/
protected $name;
/**
* @var array
*/
protected $defaultTableOptions = [];
/**
* @return bool
*/
public function hasExplicitForeignKeyIndexes()
{
return $this->hasExplicitForeignKeyIndexes;
}
/**
* @param bool $flag
*
* @return void
*/
public function setExplicitForeignKeyIndexes($flag)
{
$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
*/
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 array
*/
public function getDefaultTableOptions()
{
return $this->defaultTableOptions;
}
/**
* @param array $defaultTableOptions
*
* @return void
*/
public function setDefaultTableOptions(array $defaultTableOptions)
{
$this->defaultTableOptions = $defaultTableOptions;
}
}

View File

@@ -0,0 +1,205 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use \Doctrine\DBAL\Platforms\AbstractPlatform;
use function array_merge;
/**
* Schema Diff.
*
* @link www.doctrine-project.org
* @copyright Copyright (C) 2005-2009 eZ Systems AS. All rights reserved.
* @license http://ez.no/licenses/new_bsd New BSD License
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class SchemaDiff
{
/**
* @var \Doctrine\DBAL\Schema\Schema
*/
public $fromSchema;
/**
* All added namespaces.
*
* @var string[]
*/
public $newNamespaces = [];
/**
* All removed namespaces.
*
* @var string[]
*/
public $removedNamespaces = [];
/**
* All added tables.
*
* @var \Doctrine\DBAL\Schema\Table[]
*/
public $newTables = [];
/**
* All changed tables.
*
* @var \Doctrine\DBAL\Schema\TableDiff[]
*/
public $changedTables = [];
/**
* All removed tables.
*
* @var \Doctrine\DBAL\Schema\Table[]
*/
public $removedTables = [];
/**
* @var \Doctrine\DBAL\Schema\Sequence[]
*/
public $newSequences = [];
/**
* @var \Doctrine\DBAL\Schema\Sequence[]
*/
public $changedSequences = [];
/**
* @var \Doctrine\DBAL\Schema\Sequence[]
*/
public $removedSequences = [];
/**
* @var \Doctrine\DBAL\Schema\ForeignKeyConstraint[]
*/
public $orphanedForeignKeys = [];
/**
* Constructs an SchemaDiff object.
*
* @param \Doctrine\DBAL\Schema\Table[] $newTables
* @param \Doctrine\DBAL\Schema\TableDiff[] $changedTables
* @param \Doctrine\DBAL\Schema\Table[] $removedTables
* @param \Doctrine\DBAL\Schema\Schema|null $fromSchema
*/
public function __construct($newTables = [], $changedTables = [], $removedTables = [], Schema $fromSchema = null)
{
$this->newTables = $newTables;
$this->changedTables = $changedTables;
$this->removedTables = $removedTables;
$this->fromSchema = $fromSchema;
}
/**
* 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.
*
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
*
* @return array
*/
public function toSaveSql(AbstractPlatform $platform)
{
return $this->_toSql($platform, true);
}
/**
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
*
* @return array
*/
public function toSql(AbstractPlatform $platform)
{
return $this->_toSql($platform, false);
}
/**
* @param \Doctrine\DBAL\Platforms\AbstractPlatform $platform
* @param bool $saveMode
*
* @return array
*/
protected function _toSql(AbstractPlatform $platform, $saveMode = false)
{
$sql = [];
if ($platform->supportsSchemas()) {
foreach ($this->newNamespaces as $newNamespace) {
$sql[] = $platform->getCreateSchemaSQL($newNamespace);
}
}
if ($platform->supportsForeignKeyConstraints() && $saveMode == false) {
foreach ($this->orphanedForeignKeys as $orphanedForeignKey) {
$sql[] = $platform->getDropForeignKeySQL($orphanedForeignKey, $orphanedForeignKey->getLocalTable());
}
}
if ($platform->supportsSequences() == true) {
foreach ($this->changedSequences as $sequence) {
$sql[] = $platform->getAlterSequenceSQL($sequence);
}
if ($saveMode === false) {
foreach ($this->removedSequences as $sequence) {
$sql[] = $platform->getDropSequenceSQL($sequence);
}
}
foreach ($this->newSequences as $sequence) {
$sql[] = $platform->getCreateSequenceSQL($sequence);
}
}
$foreignKeySql = [];
foreach ($this->newTables as $table) {
$sql = array_merge(
$sql,
$platform->getCreateTableSQL($table, AbstractPlatform::CREATE_INDEXES)
);
if ($platform->supportsForeignKeyConstraints()) {
foreach ($table->getForeignKeys() as $foreignKey) {
$foreignKeySql[] = $platform->getCreateForeignKeySQL($foreignKey, $table);
}
}
}
$sql = array_merge($sql, $foreignKeySql);
if ($saveMode === false) {
foreach ($this->removedTables as $table) {
$sql[] = $platform->getDropTableSQL($table);
}
}
foreach ($this->changedTables as $tableDiff) {
$sql = array_merge($sql, $platform->getAlterTableSQL($tableDiff));
}
return $sql;
}
}

View File

@@ -0,0 +1,184 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use function implode;
use function sprintf;
class SchemaException extends \Doctrine\DBAL\DBALException
{
const TABLE_DOESNT_EXIST = 10;
const TABLE_ALREADY_EXISTS = 20;
const COLUMN_DOESNT_EXIST = 30;
const COLUMN_ALREADY_EXISTS = 40;
const INDEX_DOESNT_EXIST = 50;
const INDEX_ALREADY_EXISTS = 60;
const SEQUENCE_DOENST_EXIST = 70;
const SEQUENCE_ALREADY_EXISTS = 80;
const INDEX_INVALID_NAME = 90;
const FOREIGNKEY_DOESNT_EXIST = 100;
const NAMESPACE_ALREADY_EXISTS = 110;
/**
* @param string $tableName
*
* @return \Doctrine\DBAL\Schema\SchemaException
*/
public static function tableDoesNotExist($tableName)
{
return new self("There is no table with name '".$tableName."' in the schema.", self::TABLE_DOESNT_EXIST);
}
/**
* @param string $indexName
*
* @return \Doctrine\DBAL\Schema\SchemaException
*/
public static function indexNameInvalid($indexName)
{
return new self("Invalid index-name $indexName given, has to be [a-zA-Z0-9_]", self::INDEX_INVALID_NAME);
}
/**
* @param string $indexName
* @param string $table
*
* @return \Doctrine\DBAL\Schema\SchemaException
*/
public static function indexDoesNotExist($indexName, $table)
{
return new self("Index '$indexName' does not exist on table '$table'.", self::INDEX_DOESNT_EXIST);
}
/**
* @param string $indexName
* @param string $table
*
* @return \Doctrine\DBAL\Schema\SchemaException
*/
public static function indexAlreadyExists($indexName, $table)
{
return new self("An index with name '$indexName' was already defined on table '$table'.", self::INDEX_ALREADY_EXISTS);
}
/**
* @param string $columnName
* @param string $table
*
* @return \Doctrine\DBAL\Schema\SchemaException
*/
public static function columnDoesNotExist($columnName, $table)
{
return new self("There is no column with name '$columnName' on table '$table'.", self::COLUMN_DOESNT_EXIST);
}
/**
* @param string $namespaceName
*
* @return \Doctrine\DBAL\Schema\SchemaException
*/
public static function namespaceAlreadyExists($namespaceName)
{
return new self(
sprintf("The namespace with name '%s' already exists.", $namespaceName),
self::NAMESPACE_ALREADY_EXISTS
);
}
/**
* @param string $tableName
*
* @return \Doctrine\DBAL\Schema\SchemaException
*/
public static function tableAlreadyExists($tableName)
{
return new self("The table with name '".$tableName."' already exists.", self::TABLE_ALREADY_EXISTS);
}
/**
* @param string $tableName
* @param string $columnName
*
* @return \Doctrine\DBAL\Schema\SchemaException
*/
public static function columnAlreadyExists($tableName, $columnName)
{
return new self(
"The column '".$columnName."' on table '".$tableName."' already exists.", self::COLUMN_ALREADY_EXISTS
);
}
/**
* @param string $sequenceName
*
* @return \Doctrine\DBAL\Schema\SchemaException
*/
public static function sequenceAlreadyExists($sequenceName)
{
return new self("The sequence '".$sequenceName."' already exists.", self::SEQUENCE_ALREADY_EXISTS);
}
/**
* @param string $sequenceName
*
* @return \Doctrine\DBAL\Schema\SchemaException
*/
public static function sequenceDoesNotExist($sequenceName)
{
return new self("There exists no sequence with the name '".$sequenceName."'.", self::SEQUENCE_DOENST_EXIST);
}
/**
* @param string $fkName
* @param string $table
*
* @return \Doctrine\DBAL\Schema\SchemaException
*/
public static function foreignKeyDoesNotExist($fkName, $table)
{
return new self("There exists no foreign key with the name '$fkName' on table '$table'.", self::FOREIGNKEY_DOESNT_EXIST);
}
/**
* @param \Doctrine\DBAL\Schema\Table $localTable
* @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey
*
* @return \Doctrine\DBAL\Schema\SchemaException
*/
public static function namedForeignKeyRequired(Table $localTable, ForeignKeyConstraint $foreignKey)
{
return new self(
"The performed schema operation on ".$localTable->getName()." requires a named foreign key, ".
"but the given foreign key from (".implode(", ", $foreignKey->getColumns()).") onto foreign table ".
"'".$foreignKey->getForeignTableName()."' (".implode(", ", $foreignKey->getForeignColumns()).") is currently ".
"unnamed."
);
}
/**
* @param string $changeName
*
* @return \Doctrine\DBAL\Schema\SchemaException
*/
public static function alterTableChangeNotSupported($changeName)
{
return new self("Alter table change not supported, given '$changeName'");
}
}

View File

@@ -0,0 +1,169 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Schema\Visitor\Visitor;
use function count;
use function is_numeric;
use function sprintf;
/**
* Sequence structure.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class Sequence extends AbstractAsset
{
/**
* @var int
*/
protected $allocationSize = 1;
/**
* @var int
*/
protected $initialValue = 1;
/**
* @var int|null
*/
protected $cache = null;
/**
* @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->allocationSize = is_numeric($allocationSize) ? $allocationSize : 1;
$this->initialValue = is_numeric($initialValue) ? $initialValue : 1;
$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 \Doctrine\DBAL\Schema\Sequence
*/
public function setAllocationSize($allocationSize)
{
$this->allocationSize = is_numeric($allocationSize) ? $allocationSize : 1;
return $this;
}
/**
* @param int $initialValue
*
* @return \Doctrine\DBAL\Schema\Sequence
*/
public function setInitialValue($initialValue)
{
$this->initialValue = is_numeric($initialValue) ? $initialValue : 1;
return $this;
}
/**
* @param int $cache
*
* @return \Doctrine\DBAL\Schema\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.
*
* @param \Doctrine\DBAL\Schema\Table $table
*
* @return bool
*/
public function isAutoIncrementsFor(Table $table)
{
if ( ! $table->hasPrimaryKey()) {
return false;
}
$pkColumns = $table->getPrimaryKey()->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;
}
/**
* @param \Doctrine\DBAL\Schema\Visitor\Visitor $visitor
*
* @return void
*/
public function visit(Visitor $visitor)
{
$visitor->acceptSequence($this);
}
}

View File

@@ -0,0 +1,483 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\FetchMode;
use Doctrine\DBAL\Types\StringType;
use Doctrine\DBAL\Types\TextType;
use Doctrine\DBAL\Types\Type;
use const CASE_LOWER;
use function array_change_key_case;
use function array_map;
use function array_reverse;
use function array_values;
use function explode;
use function file_exists;
use function preg_match;
use function preg_match_all;
use function preg_quote;
use function preg_replace;
use function rtrim;
use function sprintf;
use function str_replace;
use function strpos;
use function strtolower;
use function trim;
use function unlink;
use function usort;
/**
* Sqlite SchemaManager.
*
* @author Konsta Vesterinen <kvesteri@cc.hut.fi>
* @author Lukas Smith <smith@pooteeweet.org> (PEAR MDB2 library)
* @author Jonathan H. Wage <jonwage@gmail.com>
* @author Martin Hasoň <martin.hason@gmail.com>
* @since 2.0
*/
class SqliteSchemaManager extends AbstractSchemaManager
{
/**
* {@inheritdoc}
*/
public function dropDatabase($database)
{
if (file_exists($database)) {
unlink($database);
}
}
/**
* {@inheritdoc}
*/
public function createDatabase($database)
{
$params = $this->_conn->getParams();
$driver = $params['driver'];
$options = [
'driver' => $driver,
'path' => $database
];
$conn = \Doctrine\DBAL\DriverManager::getConnection($options);
$conn->connect();
$conn->close();
}
/**
* {@inheritdoc}
*/
public function renameTable($name, $newName)
{
$tableDiff = new TableDiff($name);
$tableDiff->fromTable = $this->listTableDetails($name);
$tableDiff->newName = $newName;
$this->alterTable($tableDiff);
}
/**
* {@inheritdoc}
*/
public function createForeignKey(ForeignKeyConstraint $foreignKey, $table)
{
$tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table);
$tableDiff->addedForeignKeys[] = $foreignKey;
$this->alterTable($tableDiff);
}
/**
* {@inheritdoc}
*/
public function dropAndCreateForeignKey(ForeignKeyConstraint $foreignKey, $table)
{
$tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table);
$tableDiff->changedForeignKeys[] = $foreignKey;
$this->alterTable($tableDiff);
}
/**
* {@inheritdoc}
*/
public function dropForeignKey($foreignKey, $table)
{
$tableDiff = $this->getTableDiffForAlterForeignKey($foreignKey, $table);
$tableDiff->removedForeignKeys[] = $foreignKey;
$this->alterTable($tableDiff);
}
/**
* {@inheritdoc}
*/
public function listTableForeignKeys($table, $database = null)
{
if (null === $database) {
$database = $this->_conn->getDatabase();
}
$sql = $this->_platform->getListTableForeignKeysSQL($table, $database);
$tableForeignKeys = $this->_conn->fetchAll($sql);
if ( ! empty($tableForeignKeys)) {
$createSql = $this->_conn->fetchAll("SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' AND name = '$table'");
$createSql = $createSql[0]['sql'] ?? '';
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)) {
$names = array_reverse($match[1]);
$deferrable = array_reverse($match[2]);
$deferred = array_reverse($match[3]);
} else {
$names = $deferrable = $deferred = [];
}
foreach ($tableForeignKeys as $key => $value) {
$id = $value['id'];
$tableForeignKeys[$key]['constraint_name'] = isset($names[$id]) && '' != $names[$id] ? $names[$id] : $id;
$tableForeignKeys[$key]['deferrable'] = isset($deferrable[$id]) && strtolower($deferrable[$id]) === 'deferrable';
$tableForeignKeys[$key]['deferred'] = isset($deferred[$id]) && strtolower($deferred[$id]) === 'deferred';
}
}
return $this->_getPortableTableForeignKeysList($tableForeignKeys);
}
/**
* {@inheritdoc}
*/
protected function _getPortableTableDefinition($table)
{
return $table['name'];
}
/**
* {@inheritdoc}
*
* @license New BSD License
* @link http://ezcomponents.org/docs/api/trunk/DatabaseSchema/ezcDbSchemaPgsqlReader.html
*/
protected function _getPortableTableIndexesList($tableIndexes, $tableName=null)
{
$indexBuffer = [];
// fetch primary
$stmt = $this->_conn->executeQuery("PRAGMA TABLE_INFO ('$tableName')");
$indexArray = $stmt->fetchAll(FetchMode::ASSOCIATIVE);
usort($indexArray, function($a, $b) {
if ($a['pk'] == $b['pk']) {
return $a['cid'] - $b['cid'];
}
return $a['pk'] - $b['pk'];
});
foreach ($indexArray as $indexColumnRow) {
if ($indexColumnRow['pk'] != "0") {
$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) {
$keyName = $tableIndex['name'];
$idx = [];
$idx['key_name'] = $keyName;
$idx['primary'] = false;
$idx['non_unique'] = $tableIndex['unique']?false:true;
$stmt = $this->_conn->executeQuery("PRAGMA INDEX_INFO ('{$keyName}')");
$indexArray = $stmt->fetchAll(FetchMode::ASSOCIATIVE);
foreach ($indexArray as $indexColumnRow) {
$idx['column_name'] = $indexColumnRow['name'];
$indexBuffer[] = $idx;
}
}
}
return parent::_getPortableTableIndexesList($indexBuffer, $tableName);
}
/**
* {@inheritdoc}
*/
protected function _getPortableTableIndexDefinition($tableIndex)
{
return [
'name' => $tableIndex['name'],
'unique' => (bool) $tableIndex['unique']
];
}
/**
* {@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 ('0' != $tableColumn['pk']) {
$autoincrementCount++;
if (null === $autoincrementColumn && 'integer' == strtolower($tableColumn['type'])) {
$autoincrementColumn = $tableColumn['name'];
}
}
}
if (1 == $autoincrementCount && null !== $autoincrementColumn) {
foreach ($list as $column) {
if ($autoincrementColumn == $column->getName()) {
$column->setAutoincrement(true);
}
}
}
// inspect column collation and comments
$createSql = $this->_conn->fetchAll("SELECT sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type = 'table' AND name = '$table'");
$createSql = $createSql[0]['sql'] ?? '';
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) {
$type = $this->extractDoctrineTypeFromComment($comment, null);
if (null !== $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 strings wrapped in single quotes, so we need to strip them
$default = preg_replace("/^'(.*)'$/", '\1', $default);
}
$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";
}
list($precision, $scale) = array_map('trim', explode(',', $tableColumn['length']));
}
$length = null;
break;
}
$options = [
'length' => $length,
'unsigned' => (bool) $unsigned,
'fixed' => $fixed,
'notnull' => $notnull,
'default' => $default,
'precision' => $precision,
'scale' => $scale,
'autoincrement' => false,
];
return new Column($tableColumn['name'], \Doctrine\DBAL\Types\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);
$name = $value['constraint_name'];
if ( ! isset($list[$name])) {
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[$name] = [
'name' => $name,
'local' => [],
'foreign' => [],
'foreignTable' => $value['table'],
'onDelete' => $value['on_delete'],
'onUpdate' => $value['on_update'],
'deferrable' => $value['deferrable'],
'deferred'=> $value['deferred'],
];
}
$list[$name]['local'][] = $value['from'];
$list[$name]['foreign'][] = $value['to'];
}
$result = [];
foreach ($list as $constraint) {
$result[] = new ForeignKeyConstraint(
array_values($constraint['local']), $constraint['foreignTable'],
array_values($constraint['foreign']), $constraint['name'],
[
'onDelete' => $constraint['onDelete'],
'onUpdate' => $constraint['onUpdate'],
'deferrable' => $constraint['deferrable'],
'deferred'=> $constraint['deferred'],
]
);
}
return $result;
}
/**
* @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey
* @param \Doctrine\DBAL\Schema\Table|string $table
*
* @return \Doctrine\DBAL\Schema\TableDiff
*
* @throws \Doctrine\DBAL\DBALException
*/
private function getTableDiffForAlterForeignKey(ForeignKeyConstraint $foreignKey, $table)
{
if ( ! $table instanceof Table) {
$tableDetails = $this->tryMethod('listTableDetails', $table);
if (false === $table) {
throw new DBALException(sprintf('Sqlite schema manager requires to modify foreign keys table definition "%s".', $table));
}
$table = $tableDetails;
}
$tableDiff = new TableDiff($table->getName());
$tableDiff->fromTable = $table;
return $tableDiff;
}
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 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;
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema\Synchronizer;
use Doctrine\DBAL\Connection;
/**
* Abstract schema synchronizer with methods for executing batches of SQL.
*/
abstract class AbstractSchemaSynchronizer implements SchemaSynchronizer
{
/**
* @var \Doctrine\DBAL\Connection
*/
protected $conn;
/**
* @param \Doctrine\DBAL\Connection $conn
*/
public function __construct(Connection $conn)
{
$this->conn = $conn;
}
/**
* @param array $sql
*/
protected function processSqlSafely(array $sql)
{
foreach ($sql as $s) {
try {
$this->conn->exec($s);
} catch (\Exception $e) {
}
}
}
/**
* @param array $sql
*/
protected function processSql(array $sql)
{
foreach ($sql as $s) {
$this->conn->exec($s);
}
}
}

View File

@@ -0,0 +1,101 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema\Synchronizer;
use Doctrine\DBAL\Schema\Schema;
/**
* The synchronizer knows how to synchronize a schema with the configured
* database.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
interface SchemaSynchronizer
{
/**
* Gets the SQL statements that can be executed to create the schema.
*
* @param \Doctrine\DBAL\Schema\Schema $createSchema
*
* @return array
*/
function getCreateSchema(Schema $createSchema);
/**
* Gets the SQL Statements to update given schema with the underlying db.
*
* @param \Doctrine\DBAL\Schema\Schema $toSchema
* @param bool $noDrops
*
* @return array
*/
function getUpdateSchema(Schema $toSchema, $noDrops = false);
/**
* Gets the SQL Statements to drop the given schema from underlying db.
*
* @param \Doctrine\DBAL\Schema\Schema $dropSchema
*
* @return array
*/
function getDropSchema(Schema $dropSchema);
/**
* Gets the SQL statements to drop all schema assets from underlying db.
*
* @return array
*/
function getDropAllSchema();
/**
* Creates the Schema.
*
* @param \Doctrine\DBAL\Schema\Schema $createSchema
*
* @return void
*/
function createSchema(Schema $createSchema);
/**
* Updates the Schema to new schema version.
*
* @param \Doctrine\DBAL\Schema\Schema $toSchema
* @param bool $noDrops
*
* @return void
*/
function updateSchema(Schema $toSchema, $noDrops = false);
/**
* Drops the given database schema from the underlying db.
*
* @param \Doctrine\DBAL\Schema\Schema $dropSchema
*
* @return void
*/
function dropSchema(Schema $dropSchema);
/**
* Drops all assets from the underlying db.
*
* @return void
*/
function dropAllSchema();
}

View File

@@ -0,0 +1,177 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema\Synchronizer;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\Comparator;
use Doctrine\DBAL\Schema\Visitor\DropSchemaSqlCollector;
use function count;
/**
* Schema Synchronizer for Default DBAL Connection.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class SingleDatabaseSynchronizer extends AbstractSchemaSynchronizer
{
/**
* @var \Doctrine\DBAL\Platforms\AbstractPlatform
*/
private $platform;
/**
* @param \Doctrine\DBAL\Connection $conn
*/
public function __construct(Connection $conn)
{
parent::__construct($conn);
$this->platform = $conn->getDatabasePlatform();
}
/**
* {@inheritdoc}
*/
public function getCreateSchema(Schema $createSchema)
{
return $createSchema->toSql($this->platform);
}
/**
* {@inheritdoc}
*/
public function getUpdateSchema(Schema $toSchema, $noDrops = false)
{
$comparator = new Comparator();
$sm = $this->conn->getSchemaManager();
$fromSchema = $sm->createSchema();
$schemaDiff = $comparator->compare($fromSchema, $toSchema);
if ($noDrops) {
return $schemaDiff->toSaveSql($this->platform);
}
return $schemaDiff->toSql($this->platform);
}
/**
* {@inheritdoc}
*/
public function getDropSchema(Schema $dropSchema)
{
$visitor = new DropSchemaSqlCollector($this->platform);
$sm = $this->conn->getSchemaManager();
$fullSchema = $sm->createSchema();
foreach ($fullSchema->getTables() as $table) {
if ($dropSchema->hasTable($table->getName())) {
$visitor->acceptTable($table);
}
foreach ($table->getForeignKeys() as $foreignKey) {
if ( ! $dropSchema->hasTable($table->getName())) {
continue;
}
if ( ! $dropSchema->hasTable($foreignKey->getForeignTableName())) {
continue;
}
$visitor->acceptForeignKey($table, $foreignKey);
}
}
if ( ! $this->platform->supportsSequences()) {
return $visitor->getQueries();
}
foreach ($dropSchema->getSequences() as $sequence) {
$visitor->acceptSequence($sequence);
}
foreach ($dropSchema->getTables() as $table) {
if ( ! $table->hasPrimaryKey()) {
continue;
}
$columns = $table->getPrimaryKey()->getColumns();
if (count($columns) > 1) {
continue;
}
$checkSequence = $table->getName() . "_" . $columns[0] . "_seq";
if ($fullSchema->hasSequence($checkSequence)) {
$visitor->acceptSequence($fullSchema->getSequence($checkSequence));
}
}
return $visitor->getQueries();
}
/**
* {@inheritdoc}
*/
public function getDropAllSchema()
{
$sm = $this->conn->getSchemaManager();
$visitor = new DropSchemaSqlCollector($this->platform);
/* @var $schema \Doctrine\DBAL\Schema\Schema */
$schema = $sm->createSchema();
$schema->visit($visitor);
return $visitor->getQueries();
}
/**
* {@inheritdoc}
*/
public function createSchema(Schema $createSchema)
{
$this->processSql($this->getCreateSchema($createSchema));
}
/**
* {@inheritdoc}
*/
public function updateSchema(Schema $toSchema, $noDrops = false)
{
$this->processSql($this->getUpdateSchema($toSchema, $noDrops));
}
/**
* {@inheritdoc}
*/
public function dropSchema(Schema $dropSchema)
{
$this->processSqlSafely($this->getDropSchema($dropSchema));
}
/**
* {@inheritdoc}
*/
public function dropAllSchema()
{
$this->processSql($this->getDropAllSchema());
}
}

View File

@@ -0,0 +1,872 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Schema\Visitor\Visitor;
use Doctrine\DBAL\DBALException;
use const ARRAY_FILTER_USE_KEY;
use function array_filter;
use function array_merge;
use function in_array;
use function is_numeric;
use function is_string;
use function preg_match;
use function strlen;
use function strtolower;
/**
* Object Representation of a table.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class Table extends AbstractAsset
{
/**
* @var string
*/
protected $_name = null;
/**
* @var Column[]
*/
protected $_columns = [];
/**
* @var Index[]
*/
private $implicitIndexes = [];
/**
* @var Index[]
*/
protected $_indexes = [];
/**
* @var string
*/
protected $_primaryKeyName = false;
/**
* @var ForeignKeyConstraint[]
*/
protected $_fkConstraints = [];
/**
* @var array
*/
protected $_options = [];
/**
* @var SchemaConfig|null
*/
protected $_schemaConfig = null;
/**
* @param string $tableName
* @param Column[] $columns
* @param Index[] $indexes
* @param ForeignKeyConstraint[] $fkConstraints
* @param int $idGeneratorType
* @param array $options
*
* @throws DBALException
*/
public function __construct($tableName, array $columns=[], array $indexes=[], array $fkConstraints=[], $idGeneratorType = 0, array $options=[])
{
if (strlen($tableName) == 0) {
throw DBALException::invalidTableName($tableName);
}
$this->_setName($tableName);
foreach ($columns as $column) {
$this->_addColumn($column);
}
foreach ($indexes as $idx) {
$this->_addIndex($idx);
}
foreach ($fkConstraints as $constraint) {
$this->_addForeignKeyConstraint($constraint);
}
$this->_options = $options;
}
/**
* @param SchemaConfig $schemaConfig
*
* @return void
*/
public function setSchemaConfig(SchemaConfig $schemaConfig)
{
$this->_schemaConfig = $schemaConfig;
}
/**
* @return int
*/
protected function _getMaxIdentifierLength()
{
if ($this->_schemaConfig instanceof SchemaConfig) {
return $this->_schemaConfig->getMaxIdentifierLength();
}
return 63;
}
/**
* Sets the Primary Key.
*
* @param array $columns
* @param string|boolean $indexName
*
* @return self
*/
public function setPrimaryKey(array $columns, $indexName = false)
{
$this->_addIndex($this->_createIndex($columns, $indexName ?: "primary", true, true));
foreach ($columns as $columnName) {
$column = $this->getColumn($columnName);
$column->setNotnull(true);
}
return $this;
}
/**
* @param array $columnNames
* @param string|null $indexName
* @param array $flags
* @param array $options
*
* @return self
*/
public function addIndex(array $columnNames, $indexName = null, array $flags = [], array $options = [])
{
if ($indexName == null) {
$indexName = $this->_generateIdentifierName(
array_merge([$this->getName()], $columnNames), "idx", $this->_getMaxIdentifierLength()
);
}
return $this->_addIndex($this->_createIndex($columnNames, $indexName, false, false, $flags, $options));
}
/**
* Drops the primary key from this table.
*
* @return void
*/
public function dropPrimaryKey()
{
$this->dropIndex($this->_primaryKeyName);
$this->_primaryKeyName = false;
}
/**
* Drops an index from this table.
*
* @param string $indexName The index name.
*
* @return void
*
* @throws SchemaException If the index does not exist.
*/
public function dropIndex($indexName)
{
$indexName = $this->normalizeIdentifier($indexName);
if ( ! $this->hasIndex($indexName)) {
throw SchemaException::indexDoesNotExist($indexName, $this->_name);
}
unset($this->_indexes[$indexName]);
}
/**
* @param array $columnNames
* @param string|null $indexName
* @param array $options
*
* @return self
*/
public function addUniqueIndex(array $columnNames, $indexName = null, array $options = [])
{
if ($indexName === null) {
$indexName = $this->_generateIdentifierName(
array_merge([$this->getName()], $columnNames), "uniq", $this->_getMaxIdentifierLength()
);
}
return $this->_addIndex($this->_createIndex($columnNames, $indexName, true, false, [], $options));
}
/**
* Renames an index.
*
* @param string $oldIndexName The name of the index to rename from.
* @param string|null $newIndexName The name of the index to rename to.
* If null is given, the index name will be auto-generated.
*
* @return self This table instance.
*
* @throws SchemaException if no index exists for the given current name
* or if an index with the given new name already exists on this table.
*/
public function renameIndex($oldIndexName, $newIndexName = null)
{
$oldIndexName = $this->normalizeIdentifier($oldIndexName);
$normalizedNewIndexName = $this->normalizeIdentifier($newIndexName);
if ($oldIndexName === $normalizedNewIndexName) {
return $this;
}
if ( ! $this->hasIndex($oldIndexName)) {
throw SchemaException::indexDoesNotExist($oldIndexName, $this->_name);
}
if ($this->hasIndex($normalizedNewIndexName)) {
throw SchemaException::indexAlreadyExists($normalizedNewIndexName, $this->_name);
}
$oldIndex = $this->_indexes[$oldIndexName];
if ($oldIndex->isPrimary()) {
$this->dropPrimaryKey();
return $this->setPrimaryKey($oldIndex->getColumns(), $newIndexName);
}
unset($this->_indexes[$oldIndexName]);
if ($oldIndex->isUnique()) {
return $this->addUniqueIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getOptions());
}
return $this->addIndex($oldIndex->getColumns(), $newIndexName, $oldIndex->getFlags(), $oldIndex->getOptions());
}
/**
* Checks if an index begins in the order of the given columns.
*
* @param array $columnsNames
*
* @return bool
*/
public function columnsAreIndexed(array $columnsNames)
{
foreach ($this->getIndexes() as $index) {
/* @var $index Index */
if ($index->spansColumns($columnsNames)) {
return true;
}
}
return false;
}
/**
* @param array $columnNames
* @param string $indexName
* @param bool $isUnique
* @param bool $isPrimary
* @param array $flags
* @param array $options
*
* @return Index
*
* @throws SchemaException
*/
private function _createIndex(array $columnNames, $indexName, $isUnique, $isPrimary, array $flags = [], array $options = [])
{
if (preg_match('(([^a-zA-Z0-9_]+))', $this->normalizeIdentifier($indexName))) {
throw SchemaException::indexNameInvalid($indexName);
}
foreach ($columnNames as $columnName => $indexColOptions) {
if (is_numeric($columnName) && is_string($indexColOptions)) {
$columnName = $indexColOptions;
}
if ( ! $this->hasColumn($columnName)) {
throw SchemaException::columnDoesNotExist($columnName, $this->_name);
}
}
return new Index($indexName, $columnNames, $isUnique, $isPrimary, $flags, $options);
}
/**
* @param string $columnName
* @param string $typeName
* @param array $options
*
* @return Column
*/
public function addColumn($columnName, $typeName, array $options=[])
{
$column = new Column($columnName, Type::getType($typeName), $options);
$this->_addColumn($column);
return $column;
}
/**
* Renames a Column.
*
* @param string $oldColumnName
* @param string $newColumnName
*
* @deprecated
*
* @throws DBALException
*/
public function renameColumn($oldColumnName, $newColumnName)
{
throw new DBALException("Table#renameColumn() was removed, because it drops and recreates " .
"the column instead. There is no fix available, because a schema diff cannot reliably detect if a " .
"column was renamed or one column was created and another one dropped.");
}
/**
* Change Column Details.
*
* @param string $columnName
* @param array $options
*
* @return self
*/
public function changeColumn($columnName, array $options)
{
$column = $this->getColumn($columnName);
$column->setOptions($options);
return $this;
}
/**
* Drops a Column from the Table.
*
* @param string $columnName
*
* @return self
*/
public function dropColumn($columnName)
{
$columnName = $this->normalizeIdentifier($columnName);
unset($this->_columns[$columnName]);
return $this;
}
/**
* Adds a foreign key constraint.
*
* Name is inferred from the local columns.
*
* @param Table|string $foreignTable Table schema instance or table name
* @param array $localColumnNames
* @param array $foreignColumnNames
* @param array $options
* @param string|null $constraintName
*
* @return self
*/
public function addForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=[], $constraintName = null)
{
$constraintName = $constraintName ?: $this->_generateIdentifierName(array_merge((array) $this->getName(), $localColumnNames), "fk", $this->_getMaxIdentifierLength());
return $this->addNamedForeignKeyConstraint($constraintName, $foreignTable, $localColumnNames, $foreignColumnNames, $options);
}
/**
* Adds a foreign key constraint.
*
* Name is to be generated by the database itself.
*
* @deprecated Use {@link addForeignKeyConstraint}
*
* @param Table|string $foreignTable Table schema instance or table name
* @param array $localColumnNames
* @param array $foreignColumnNames
* @param array $options
*
* @return self
*/
public function addUnnamedForeignKeyConstraint($foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=[])
{
return $this->addForeignKeyConstraint($foreignTable, $localColumnNames, $foreignColumnNames, $options);
}
/**
* Adds a foreign key constraint with a given name.
*
* @deprecated Use {@link addForeignKeyConstraint}
*
* @param string $name
* @param Table|string $foreignTable Table schema instance or table name
* @param array $localColumnNames
* @param array $foreignColumnNames
* @param array $options
*
* @return self
*
* @throws SchemaException
*/
public function addNamedForeignKeyConstraint($name, $foreignTable, array $localColumnNames, array $foreignColumnNames, array $options=[])
{
if ($foreignTable instanceof Table) {
foreach ($foreignColumnNames as $columnName) {
if ( ! $foreignTable->hasColumn($columnName)) {
throw SchemaException::columnDoesNotExist($columnName, $foreignTable->getName());
}
}
}
foreach ($localColumnNames as $columnName) {
if ( ! $this->hasColumn($columnName)) {
throw SchemaException::columnDoesNotExist($columnName, $this->_name);
}
}
$constraint = new ForeignKeyConstraint(
$localColumnNames, $foreignTable, $foreignColumnNames, $name, $options
);
$this->_addForeignKeyConstraint($constraint);
return $this;
}
/**
* @param string $name
* @param string $value
*
* @return self
*/
public function addOption($name, $value)
{
$this->_options[$name] = $value;
return $this;
}
/**
* @param Column $column
*
* @return void
*
* @throws SchemaException
*/
protected function _addColumn(Column $column)
{
$columnName = $column->getName();
$columnName = $this->normalizeIdentifier($columnName);
if (isset($this->_columns[$columnName])) {
throw SchemaException::columnAlreadyExists($this->getName(), $columnName);
}
$this->_columns[$columnName] = $column;
}
/**
* Adds an index to the table.
*
* @param Index $indexCandidate
*
* @return self
*
* @throws SchemaException
*/
protected function _addIndex(Index $indexCandidate)
{
$indexName = $indexCandidate->getName();
$indexName = $this->normalizeIdentifier($indexName);
$replacedImplicitIndexes = [];
foreach ($this->implicitIndexes as $name => $implicitIndex) {
if ($implicitIndex->isFullfilledBy($indexCandidate) && isset($this->_indexes[$name])) {
$replacedImplicitIndexes[] = $name;
}
}
if ((isset($this->_indexes[$indexName]) && ! in_array($indexName, $replacedImplicitIndexes, true)) ||
($this->_primaryKeyName != false && $indexCandidate->isPrimary())
) {
throw SchemaException::indexAlreadyExists($indexName, $this->_name);
}
foreach ($replacedImplicitIndexes as $name) {
unset($this->_indexes[$name], $this->implicitIndexes[$name]);
}
if ($indexCandidate->isPrimary()) {
$this->_primaryKeyName = $indexName;
}
$this->_indexes[$indexName] = $indexCandidate;
return $this;
}
/**
* @param ForeignKeyConstraint $constraint
*
* @return void
*/
protected function _addForeignKeyConstraint(ForeignKeyConstraint $constraint)
{
$constraint->setLocalTable($this);
if (strlen($constraint->getName())) {
$name = $constraint->getName();
} else {
$name = $this->_generateIdentifierName(
array_merge((array) $this->getName(), $constraint->getLocalColumns()), "fk", $this->_getMaxIdentifierLength()
);
}
$name = $this->normalizeIdentifier($name);
$this->_fkConstraints[$name] = $constraint;
// add an explicit index on the foreign key columns. If there is already an index that fulfils this requirements drop the request.
// In the case of __construct calling this method during hydration from schema-details all the explicitly added indexes
// lead to duplicates. This creates computation overhead in this case, however no duplicate indexes are ever added (based on columns).
$indexName = $this->_generateIdentifierName(
array_merge([$this->getName()], $constraint->getColumns()),
"idx",
$this->_getMaxIdentifierLength()
);
$indexCandidate = $this->_createIndex($constraint->getColumns(), $indexName, false, false);
foreach ($this->_indexes as $existingIndex) {
if ($indexCandidate->isFullfilledBy($existingIndex)) {
return;
}
}
$this->_addIndex($indexCandidate);
$this->implicitIndexes[$this->normalizeIdentifier($indexName)] = $indexCandidate;
}
/**
* Returns whether this table has a foreign key constraint with the given name.
*
* @param string $constraintName
*
* @return bool
*/
public function hasForeignKey($constraintName)
{
$constraintName = $this->normalizeIdentifier($constraintName);
return isset($this->_fkConstraints[$constraintName]);
}
/**
* Returns the foreign key constraint with the given name.
*
* @param string $constraintName The constraint name.
*
* @return ForeignKeyConstraint
*
* @throws SchemaException If the foreign key does not exist.
*/
public function getForeignKey($constraintName)
{
$constraintName = $this->normalizeIdentifier($constraintName);
if (!$this->hasForeignKey($constraintName)) {
throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
}
return $this->_fkConstraints[$constraintName];
}
/**
* Removes the foreign key constraint with the given name.
*
* @param string $constraintName The constraint name.
*
* @return void
*
* @throws SchemaException
*/
public function removeForeignKey($constraintName)
{
$constraintName = $this->normalizeIdentifier($constraintName);
if (!$this->hasForeignKey($constraintName)) {
throw SchemaException::foreignKeyDoesNotExist($constraintName, $this->_name);
}
unset($this->_fkConstraints[$constraintName]);
}
/**
* Returns ordered list of columns (primary keys are first, then foreign keys, then the rest)
* @return Column[]
*/
public function getColumns()
{
$primaryKeyColumns = [];
if ($this->hasPrimaryKey()) {
$primaryKeyColumns = $this->filterColumns($this->getPrimaryKey()->getColumns());
}
return array_merge($primaryKeyColumns, $this->getForeignKeyColumns(), $this->_columns);
}
/**
* Returns foreign key columns
* @return Column[]
*/
private function getForeignKeyColumns()
{
$foreignKeyColumns = [];
foreach ($this->getForeignKeys() as $foreignKey) {
/* @var $foreignKey ForeignKeyConstraint */
$foreignKeyColumns = array_merge($foreignKeyColumns, $foreignKey->getColumns());
}
return $this->filterColumns($foreignKeyColumns);
}
/**
* Returns only columns that have specified names
* @param array $columnNames
* @return Column[]
*/
private function filterColumns(array $columnNames)
{
return array_filter($this->_columns, function ($columnName) use ($columnNames) {
return in_array($columnName, $columnNames, true);
}, ARRAY_FILTER_USE_KEY);
}
/**
* Returns whether this table has a Column with the given name.
*
* @param string $columnName The column name.
*
* @return bool
*/
public function hasColumn($columnName)
{
$columnName = $this->normalizeIdentifier($columnName);
return isset($this->_columns[$columnName]);
}
/**
* Returns the Column with the given name.
*
* @param string $columnName The column name.
*
* @return Column
*
* @throws SchemaException If the column does not exist.
*/
public function getColumn($columnName)
{
$columnName = $this->normalizeIdentifier($columnName);
if ( ! $this->hasColumn($columnName)) {
throw SchemaException::columnDoesNotExist($columnName, $this->_name);
}
return $this->_columns[$columnName];
}
/**
* Returns the primary key.
*
* @return Index|null The primary key, or null if this Table has no primary key.
*/
public function getPrimaryKey()
{
if ( ! $this->hasPrimaryKey()) {
return null;
}
return $this->getIndex($this->_primaryKeyName);
}
/**
* Returns the primary key columns.
*
* @return array
*
* @throws DBALException
*/
public function getPrimaryKeyColumns()
{
if ( ! $this->hasPrimaryKey()) {
throw new DBALException("Table " . $this->getName() . " has no primary key.");
}
return $this->getPrimaryKey()->getColumns();
}
/**
* Returns whether this table has a primary key.
*
* @return bool
*/
public function hasPrimaryKey()
{
return ($this->_primaryKeyName && $this->hasIndex($this->_primaryKeyName));
}
/**
* Returns whether this table has an Index with the given name.
*
* @param string $indexName The index name.
*
* @return bool
*/
public function hasIndex($indexName)
{
$indexName = $this->normalizeIdentifier($indexName);
return (isset($this->_indexes[$indexName]));
}
/**
* Returns the Index with the given name.
*
* @param string $indexName The index name.
*
* @return Index
*
* @throws SchemaException If the index does not exist.
*/
public function getIndex($indexName)
{
$indexName = $this->normalizeIdentifier($indexName);
if ( ! $this->hasIndex($indexName)) {
throw SchemaException::indexDoesNotExist($indexName, $this->_name);
}
return $this->_indexes[$indexName];
}
/**
* @return Index[]
*/
public function getIndexes()
{
return $this->_indexes;
}
/**
* Returns the foreign key constraints.
*
* @return ForeignKeyConstraint[]
*/
public function getForeignKeys()
{
return $this->_fkConstraints;
}
/**
* @param string $name
*
* @return bool
*/
public function hasOption($name)
{
return isset($this->_options[$name]);
}
/**
* @param string $name
*
* @return mixed
*/
public function getOption($name)
{
return $this->_options[$name];
}
/**
* @return array
*/
public function getOptions()
{
return $this->_options;
}
/**
* @param Visitor $visitor
*
* @return void
*/
public function visit(Visitor $visitor)
{
$visitor->acceptTable($this);
foreach ($this->getColumns() as $column) {
$visitor->acceptColumn($this, $column);
}
foreach ($this->getIndexes() as $index) {
$visitor->acceptIndex($this, $index);
}
foreach ($this->getForeignKeys() as $constraint) {
$visitor->acceptForeignKey($this, $constraint);
}
}
/**
* Clone of a Table triggers a deep clone of all affected assets.
*
* @return void
*/
public function __clone()
{
foreach ($this->_columns as $k => $column) {
$this->_columns[$k] = clone $column;
}
foreach ($this->_indexes as $k => $index) {
$this->_indexes[$k] = clone $index;
}
foreach ($this->_fkConstraints as $k => $fk) {
$this->_fkConstraints[$k] = clone $fk;
$this->_fkConstraints[$k]->setLocalTable($this);
}
}
/**
* Normalizes a given identifier.
*
* Trims quotes and lowercases the given identifier.
*
* @param string $identifier The identifier to normalize.
*
* @return string The normalized identifier.
*/
private function normalizeIdentifier($identifier)
{
return $this->trimQuotes(strtolower($identifier));
}
}

View File

@@ -0,0 +1,170 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
use Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* Table Diff.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class TableDiff
{
/**
* @var string
*/
public $name = null;
/**
* @var string|boolean
*/
public $newName = false;
/**
* All added fields.
*
* @var \Doctrine\DBAL\Schema\Column[]
*/
public $addedColumns;
/**
* All changed fields.
*
* @var \Doctrine\DBAL\Schema\ColumnDiff[]
*/
public $changedColumns = [];
/**
* All removed fields.
*
* @var \Doctrine\DBAL\Schema\Column[]
*/
public $removedColumns = [];
/**
* Columns that are only renamed from key to column instance name.
*
* @var \Doctrine\DBAL\Schema\Column[]
*/
public $renamedColumns = [];
/**
* All added indexes.
*
* @var \Doctrine\DBAL\Schema\Index[]
*/
public $addedIndexes = [];
/**
* All changed indexes.
*
* @var \Doctrine\DBAL\Schema\Index[]
*/
public $changedIndexes = [];
/**
* All removed indexes
*
* @var \Doctrine\DBAL\Schema\Index[]
*/
public $removedIndexes = [];
/**
* Indexes that are only renamed but are identical otherwise.
*
* @var \Doctrine\DBAL\Schema\Index[]
*/
public $renamedIndexes = [];
/**
* All added foreign key definitions
*
* @var ForeignKeyConstraint[]
*/
public $addedForeignKeys = [];
/**
* All changed foreign keys
*
* @var \Doctrine\DBAL\Schema\ForeignKeyConstraint[]
*/
public $changedForeignKeys = [];
/**
* All removed foreign keys
*
* @var ForeignKeyConstraint[]|string[]
*/
public $removedForeignKeys = [];
/**
* @var \Doctrine\DBAL\Schema\Table
*/
public $fromTable;
/**
* Constructs an TableDiff object.
*
* @param string $tableName
* @param \Doctrine\DBAL\Schema\Column[] $addedColumns
* @param \Doctrine\DBAL\Schema\ColumnDiff[] $changedColumns
* @param \Doctrine\DBAL\Schema\Column[] $removedColumns
* @param \Doctrine\DBAL\Schema\Index[] $addedIndexes
* @param \Doctrine\DBAL\Schema\Index[] $changedIndexes
* @param \Doctrine\DBAL\Schema\Index[] $removedIndexes
* @param \Doctrine\DBAL\Schema\Table|null $fromTable
*/
public function __construct($tableName, $addedColumns = [],
$changedColumns = [], $removedColumns = [], $addedIndexes = [],
$changedIndexes = [], $removedIndexes = [], Table $fromTable = null)
{
$this->name = $tableName;
$this->addedColumns = $addedColumns;
$this->changedColumns = $changedColumns;
$this->removedColumns = $removedColumns;
$this->addedIndexes = $addedIndexes;
$this->changedIndexes = $changedIndexes;
$this->removedIndexes = $removedIndexes;
$this->fromTable = $fromTable;
}
/**
* @param AbstractPlatform $platform The platform to use for retrieving this table diff's name.
*
* @return \Doctrine\DBAL\Schema\Identifier
*/
public function getName(AbstractPlatform $platform)
{
return new Identifier(
$this->fromTable instanceof Table ? $this->fromTable->getQuotedName($platform) : $this->name
);
}
/**
* @return Identifier|string|bool
*/
public function getNewName()
{
return $this->newName ? new Identifier($this->newName) : $this->newName;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema;
/**
* Representation of a Database View.
*
* @link www.doctrine-project.org
* @since 1.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
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;
}
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema\Visitor;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Sequence;
use Doctrine\DBAL\Schema\Index;
/**
* Abstract Visitor with empty methods for easy extension.
*/
class AbstractVisitor implements Visitor, NamespaceVisitor
{
/**
* @param \Doctrine\DBAL\Schema\Schema $schema
*/
public function acceptSchema(Schema $schema)
{
}
/**
* {@inheritdoc}
*/
public function acceptNamespace($namespaceName)
{
}
/**
* @param \Doctrine\DBAL\Schema\Table $table
*/
public function acceptTable(Table $table)
{
}
/**
* @param \Doctrine\DBAL\Schema\Table $table
* @param \Doctrine\DBAL\Schema\Column $column
*/
public function acceptColumn(Table $table, Column $column)
{
}
/**
* @param \Doctrine\DBAL\Schema\Table $localTable
* @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $fkConstraint
*/
public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
{
}
/**
* @param \Doctrine\DBAL\Schema\Table $table
* @param \Doctrine\DBAL\Schema\Index $index
*/
public function acceptIndex(Table $table, Index $index)
{
}
/**
* @param \Doctrine\DBAL\Schema\Sequence $sequence
*/
public function acceptSequence(Sequence $sequence)
{
}
}

View File

@@ -0,0 +1,125 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema\Visitor;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Sequence;
use function array_merge;
class CreateSchemaSqlCollector extends AbstractVisitor
{
/**
* @var array
*/
private $createNamespaceQueries = [];
/**
* @var array
*/
private $createTableQueries = [];
/**
* @var array
*/
private $createSequenceQueries = [];
/**
* @var array
*/
private $createFkConstraintQueries = [];
/**
*
* @var \Doctrine\DBAL\Platforms\AbstractPlatform
*/
private $platform = null;
/**
* @param AbstractPlatform $platform
*/
public function __construct(AbstractPlatform $platform)
{
$this->platform = $platform;
}
/**
* {@inheritdoc}
*/
public function acceptNamespace($namespaceName)
{
if ($this->platform->supportsSchemas()) {
$this->createNamespaceQueries[] = $this->platform->getCreateSchemaSQL($namespaceName);
}
}
/**
* {@inheritdoc}
*/
public function acceptTable(Table $table)
{
$this->createTableQueries = array_merge($this->createTableQueries, (array) $this->platform->getCreateTableSQL($table));
}
/**
* {@inheritdoc}
*/
public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
{
if ($this->platform->supportsForeignKeyConstraints()) {
$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 array
*/
public function getQueries()
{
return array_merge(
$this->createNamespaceQueries,
$this->createTableQueries,
$this->createSequenceQueries,
$this->createFkConstraintQueries
);
}
}

View File

@@ -0,0 +1,127 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema\Visitor;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Sequence;
use Doctrine\DBAL\Schema\SchemaException;
use function strlen;
/**
* Gathers SQL statements that allow to completely drop the current schema.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class DropSchemaSqlCollector extends AbstractVisitor
{
/**
* @var \SplObjectStorage
*/
private $constraints;
/**
* @var \SplObjectStorage
*/
private $sequences;
/**
* @var \SplObjectStorage
*/
private $tables;
/**
* @var AbstractPlatform
*/
private $platform;
/**
* @param AbstractPlatform $platform
*/
public function __construct(AbstractPlatform $platform)
{
$this->platform = $platform;
$this->clearQueries();
}
/**
* {@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->constraints = new \SplObjectStorage();
$this->sequences = new \SplObjectStorage();
$this->tables = new \SplObjectStorage();
}
/**
* @return array
*/
public function getQueries()
{
$sql = [];
foreach ($this->constraints as $fkConstraint) {
$localTable = $this->constraints[$fkConstraint];
$sql[] = $this->platform->getDropForeignKeySQL($fkConstraint, $localTable);
}
foreach ($this->sequences as $sequence) {
$sql[] = $this->platform->getDropSequenceSQL($sequence);
}
foreach ($this->tables as $table) {
$sql[] = $this->platform->getDropTableSQL($table);
}
return $sql;
}
}

View File

@@ -0,0 +1,180 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema\Visitor;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use function current;
use function file_put_contents;
use function in_array;
use function mt_rand;
use function sha1;
use function strtolower;
/**
* Create a Graphviz output of a Schema.
*/
class Graphviz extends AbstractVisitor
{
/**
* @var string
*/
private $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 "' . sha1(mt_rand()) . '" {' . "\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',
]
);
}
/**
* @param \Doctrine\DBAL\Schema\Table $table
*
* @return string
*/
private function createTableLabel(Table $table)
{
// 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>';
$label .= '<TD BORDER="0" ALIGN="LEFT" BGCOLOR="#eeeeec">';
$label .= '<FONT COLOR="#2e3436" FACE="Helvetica" POINT-SIZE="12">' . $columnLabel . '</FONT>';
$label .= '</TD><TD BORDER="0" ALIGN="LEFT" BGCOLOR="#eeeeec"><FONT COLOR="#2e3436" FACE="Helvetica" POINT-SIZE="10">' . strtolower($column->getType()) . '</FONT></TD>';
$label .= '<TD BORDER="0" ALIGN="RIGHT" BGCOLOR="#eeeeec" PORT="col'.$column->getName().'">';
if ($table->hasPrimaryKey() && in_array($column->getName(), $table->getPrimaryKey()->getColumns())) {
$label .= "\xe2\x9c\xb7";
}
$label .= '</TD></TR>';
}
// End the table
$label .= '</TABLE>>';
return $label;
}
/**
* @param string $name
* @param array $options
*
* @return string
*/
private function createNode($name, $options)
{
$node = $name . " [";
foreach ($options as $key => $value) {
$node .= $key . '=' . $value . ' ';
}
$node .= "]\n";
return $node;
}
/**
* @param string $node1
* @param string $node2
* @param array $options
*
* @return string
*/
private function createNodeRelation($node1, $node2, $options)
{
$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());
}
}

View File

@@ -0,0 +1,37 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema\Visitor;
/**
* Visitor that can visit schema namespaces.
*
* @author Steve Müller <st.mueller@dzh-online.de>
* @link www.doctrine-project.org
* @since 2.5
*/
interface NamespaceVisitor
{
/**
* Accepts a schema namespace name.
*
* @param string $namespaceName The schema namespace name to accept.
*/
public function acceptNamespace($namespaceName);
}

View File

@@ -0,0 +1,94 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema\Visitor;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Sequence;
/**
* 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.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
* @since 2.2
*/
class RemoveNamespacedAssets extends AbstractVisitor
{
/**
* @var \Doctrine\DBAL\Schema\Schema
*/
private $schema;
/**
* {@inheritdoc}
*/
public function acceptSchema(Schema $schema)
{
$this->schema = $schema;
}
/**
* {@inheritdoc}
*/
public function acceptTable(Table $table)
{
if ( ! $table->isInDefaultNamespace($this->schema->getName())) {
$this->schema->dropTable($table->getName());
}
}
/**
* {@inheritdoc}
*/
public function acceptSequence(Sequence $sequence)
{
if ( ! $sequence->isInDefaultNamespace($this->schema->getName())) {
$this->schema->dropSequence($sequence->getName());
}
}
/**
* {@inheritdoc}
*/
public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint)
{
// 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())) {
$localTable->removeForeignKey($fkConstraint->getName());
}
}
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema\Visitor;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\TableDiff;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Sequence;
/**
* Visit a SchemaDiff.
*
* @link www.doctrine-project.org
* @since 2.4
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
interface SchemaDiffVisitor
{
/**
* Visit an orphaned foreign key whose table was deleted.
*
* @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey
*/
function visitOrphanedForeignKey(ForeignKeyConstraint $foreignKey);
/**
* Visit a sequence that has changed.
*
* @param \Doctrine\DBAL\Schema\Sequence $sequence
*/
function visitChangedSequence(Sequence $sequence);
/**
* Visit a sequence that has been removed.
*
* @param \Doctrine\DBAL\Schema\Sequence $sequence
*/
function visitRemovedSequence(Sequence $sequence);
/**
* @param \Doctrine\DBAL\Schema\Sequence $sequence
*/
function visitNewSequence(Sequence $sequence);
/**
* @param \Doctrine\DBAL\Schema\Table $table
*/
function visitNewTable(Table $table);
/**
* @param \Doctrine\DBAL\Schema\Table $table
* @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $foreignKey
*/
function visitNewTableForeignKey(Table $table, ForeignKeyConstraint $foreignKey);
/**
* @param \Doctrine\DBAL\Schema\Table $table
*/
function visitRemovedTable(Table $table);
/**
* @param \Doctrine\DBAL\Schema\TableDiff $tableDiff
*/
function visitChangedTable(TableDiff $tableDiff);
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
namespace Doctrine\DBAL\Schema\Visitor;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\DBAL\Schema\Column;
use Doctrine\DBAL\Schema\ForeignKeyConstraint;
use Doctrine\DBAL\Schema\Sequence;
use Doctrine\DBAL\Schema\Index;
/**
* Schema Visitor used for Validation or Generation purposes.
*
* @link www.doctrine-project.org
* @since 2.0
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
interface Visitor
{
/**
* @param \Doctrine\DBAL\Schema\Schema $schema
*
* @return void
*/
public function acceptSchema(Schema $schema);
/**
* @param \Doctrine\DBAL\Schema\Table $table
*
* @return void
*/
public function acceptTable(Table $table);
/**
* @param \Doctrine\DBAL\Schema\Table $table
* @param \Doctrine\DBAL\Schema\Column $column
*
* @return void
*/
public function acceptColumn(Table $table, Column $column);
/**
* @param \Doctrine\DBAL\Schema\Table $localTable
* @param \Doctrine\DBAL\Schema\ForeignKeyConstraint $fkConstraint
*
* @return void
*/
public function acceptForeignKey(Table $localTable, ForeignKeyConstraint $fkConstraint);
/**
* @param \Doctrine\DBAL\Schema\Table $table
* @param \Doctrine\DBAL\Schema\Index $index
*
* @return void
*/
public function acceptIndex(Table $table, Index $index);
/**
* @param \Doctrine\DBAL\Schema\Sequence $sequence
*
* @return void
*/
public function acceptSequence(Sequence $sequence);
}