init
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Schema;
|
||||
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
|
||||
/**
|
||||
* Decorates a schema collection and adds caching
|
||||
*/
|
||||
class CachedCollection implements CollectionInterface
|
||||
{
|
||||
/**
|
||||
* Cacher instance.
|
||||
*
|
||||
* @var \Psr\SimpleCache\CacheInterface
|
||||
*/
|
||||
protected CacheInterface $cacher;
|
||||
|
||||
/**
|
||||
* The decorated schema collection
|
||||
*
|
||||
* @var \Cake\Database\Schema\CollectionInterface
|
||||
*/
|
||||
protected CollectionInterface $collection;
|
||||
|
||||
/**
|
||||
* The cache key prefix
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected string $prefix;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Cake\Database\Schema\CollectionInterface $collection The collection to wrap.
|
||||
* @param string $prefix The cache key prefix to use. Typically the connection name.
|
||||
* @param \Psr\SimpleCache\CacheInterface $cacher Cacher instance.
|
||||
*/
|
||||
public function __construct(CollectionInterface $collection, string $prefix, CacheInterface $cacher)
|
||||
{
|
||||
$this->collection = $collection;
|
||||
$this->prefix = $prefix;
|
||||
$this->cacher = $cacher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function listTablesWithoutViews(): array
|
||||
{
|
||||
return $this->collection->listTablesWithoutViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function listTables(): array
|
||||
{
|
||||
return $this->collection->listTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the column metadata for a table.
|
||||
*
|
||||
* The name can include a database schema name in the form 'schema.table'.
|
||||
*
|
||||
* Caching will be applied if `cacheMetadata` key is present in the Connection
|
||||
* configuration options. Defaults to _cake_model_ when true.
|
||||
*
|
||||
* ### Options
|
||||
*
|
||||
* - `forceRefresh` - Set to true to force rebuilding the cached metadata.
|
||||
* Defaults to false.
|
||||
*
|
||||
* @param string $name The name of the table to describe.
|
||||
* @param array<string, mixed> $options The options to use, see above.
|
||||
* @return \Cake\Database\Schema\TableSchemaInterface Object with column metadata.
|
||||
* @throws \Cake\Database\Exception\DatabaseException when table cannot be described.
|
||||
*/
|
||||
public function describe(string $name, array $options = []): TableSchemaInterface
|
||||
{
|
||||
$options += ['forceRefresh' => false];
|
||||
$cacheKey = $this->cacheKey($name);
|
||||
|
||||
if (!$options['forceRefresh']) {
|
||||
$cached = $this->cacher->get($cacheKey);
|
||||
if ($cached !== null) {
|
||||
return $cached;
|
||||
}
|
||||
}
|
||||
|
||||
$table = $this->collection->describe($name, $options);
|
||||
$this->cacher->set($cacheKey, $table);
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cache key for a given name.
|
||||
*
|
||||
* @param string $name The name to get a cache key for.
|
||||
* @return string The cache key.
|
||||
*/
|
||||
public function cacheKey(string $name): string
|
||||
{
|
||||
return $this->prefix . '_' . $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a cacher.
|
||||
*
|
||||
* @param \Psr\SimpleCache\CacheInterface $cacher Cacher object
|
||||
* @return $this
|
||||
*/
|
||||
public function setCacher(CacheInterface $cacher)
|
||||
{
|
||||
$this->cacher = $cacher;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cacher.
|
||||
*
|
||||
* @return \Psr\SimpleCache\CacheInterface $cacher Cacher object
|
||||
*/
|
||||
public function getCacher(): CacheInterface
|
||||
{
|
||||
return $this->cacher;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 5.3.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Schema;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Check constraint value object
|
||||
*
|
||||
* Models a check constraint.
|
||||
*/
|
||||
class CheckConstraint extends Constraint
|
||||
{
|
||||
protected string $type = self::CHECK;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $name Constraint name.
|
||||
* @param string $expression The check constraint expression (e.g., "age >= 18")
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $name,
|
||||
protected string $expression,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the check constraint expression.
|
||||
*
|
||||
* @param string $expression The SQL expression for the check constraint
|
||||
* @return $this
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function setExpression(string $expression)
|
||||
{
|
||||
if (trim($expression) === '') {
|
||||
throw new InvalidArgumentException('Check constraint expression cannot be empty');
|
||||
}
|
||||
|
||||
$this->expression = trim($expression);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the check constraint expression.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getExpression(): string
|
||||
{
|
||||
return $this->expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a constraint to an array that is compatible
|
||||
* with the constructor.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'type' => $this->type,
|
||||
'expression' => $this->expression,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Schema;
|
||||
|
||||
use Cake\Database\Connection;
|
||||
|
||||
/**
|
||||
* Represents a database schema collection
|
||||
*
|
||||
* Gives a simple high-level schema reflection API that can be
|
||||
* decorated or extended with additional behavior like caching.
|
||||
*
|
||||
* @see \Cake\Database\Schema\SchemaDialect For lower level schema reflection API
|
||||
*/
|
||||
class Collection implements CollectionInterface
|
||||
{
|
||||
/**
|
||||
* Connection object
|
||||
*
|
||||
* @var \Cake\Database\Connection
|
||||
*/
|
||||
protected Connection $_connection;
|
||||
|
||||
/**
|
||||
* Schema dialect instance.
|
||||
*
|
||||
* @var \Cake\Database\Schema\SchemaDialect|null
|
||||
*/
|
||||
protected ?SchemaDialect $_dialect = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Cake\Database\Connection $connection The connection instance.
|
||||
*/
|
||||
public function __construct(Connection $connection)
|
||||
{
|
||||
$this->_connection = $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of tables, excluding any views, available in the current connection.
|
||||
*
|
||||
* @return array<string> The list of tables in the connected database/schema.
|
||||
*/
|
||||
public function listTablesWithoutViews(): array
|
||||
{
|
||||
return $this->getDialect()->listTablesWithoutViews();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of tables and views available in the current connection.
|
||||
*
|
||||
* @return array<string> The list of tables and views in the connected database/schema.
|
||||
*/
|
||||
public function listTables(): array
|
||||
{
|
||||
return $this->getDialect()->listTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the column metadata for a table.
|
||||
*
|
||||
* The name can include a database schema name in the form 'schema.table'.
|
||||
*
|
||||
* @param string $name The name of the table to describe.
|
||||
* @param array<string, mixed> $options Unused
|
||||
* @return \Cake\Database\Schema\TableSchemaInterface Object with column metadata.
|
||||
* @throws \Cake\Database\Exception\DatabaseException when table cannot be described.
|
||||
*/
|
||||
public function describe(string $name, array $options = []): TableSchemaInterface
|
||||
{
|
||||
return $this->getDialect()->describe($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setups the schema dialect to be used for this collection.
|
||||
*
|
||||
* @return \Cake\Database\Schema\SchemaDialect
|
||||
*/
|
||||
protected function getDialect(): SchemaDialect
|
||||
{
|
||||
return $this->_dialect ??= $this->_connection->getWriteDriver()->schemaDialect();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 4.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Schema;
|
||||
|
||||
/**
|
||||
* Represents a database schema collection
|
||||
*
|
||||
* Used to access information about the tables,
|
||||
* and other data in a database.
|
||||
*
|
||||
* @method array<string> listTablesWithoutViews() Get the list of tables available in the current connection.
|
||||
* This will exclude any views in the schema.
|
||||
*/
|
||||
interface CollectionInterface
|
||||
{
|
||||
/**
|
||||
* Get the list of tables available in the current connection.
|
||||
*
|
||||
* @return array<string> The list of tables in the connected database/schema.
|
||||
*/
|
||||
public function listTables(): array;
|
||||
|
||||
/**
|
||||
* Get the column metadata for a table.
|
||||
*
|
||||
* Caching will be applied if `cacheMetadata` key is present in the Connection
|
||||
* configuration options. Defaults to _cake_model_ when true.
|
||||
*
|
||||
* ### Options
|
||||
*
|
||||
* - `forceRefresh` - Set to true to force rebuilding the cached metadata.
|
||||
* Defaults to false.
|
||||
*
|
||||
* @param string $name The name of the table to describe.
|
||||
* @param array<string, mixed> $options The options to use, see above.
|
||||
* @return \Cake\Database\Schema\TableSchemaInterface Object with column metadata.
|
||||
* @throws \Cake\Database\Exception\DatabaseException when table cannot be described.
|
||||
*/
|
||||
public function describe(string $name, array $options = []): TableSchemaInterface;
|
||||
}
|
||||
+595
@@ -0,0 +1,595 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc.
|
||||
* (https://github.com/cakephp/migrations/tree/master/LICENSE.txt)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 5.3.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Schema;
|
||||
|
||||
use Cake\Database\TypeFactory;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Schema metadata for a single column
|
||||
*
|
||||
* Used by `TableSchema` when reflecting schema or creating tables.
|
||||
*/
|
||||
class Column
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $name Name of the column
|
||||
* @param string $type Type of the column
|
||||
* @param bool $null Whether the column allows null values
|
||||
* @param mixed $default Default value for the column
|
||||
* @param int|null $length Length of the column
|
||||
* @param bool $identity Whether the column is an identity column
|
||||
* @param string|null $generated Postgres identity option (always|default)
|
||||
* @param int|null $precision Precision for decimal or float columns
|
||||
* @param int|null $increment Increment for identity columns
|
||||
* @param string|null $after Name of the column to add this column after
|
||||
* @param string|null $onUpdate MySQL 'ON UPDATE' function
|
||||
* @param string|null $comment Comment for the column
|
||||
* @param bool|null $unsigned Whether the column is unsigned
|
||||
* @param string|null $collate Collation for the column
|
||||
* @param int|null $srid SRID for geometry fields
|
||||
* @param string|null $baseType The basic schema type if the column type is a complex/custom type.
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $name,
|
||||
protected string $type,
|
||||
protected ?bool $null = null,
|
||||
protected mixed $default = null,
|
||||
protected ?int $length = null,
|
||||
protected bool $identity = false,
|
||||
protected ?string $generated = null,
|
||||
protected ?int $precision = null,
|
||||
protected ?int $increment = null,
|
||||
protected ?string $after = null,
|
||||
protected ?string $onUpdate = null,
|
||||
protected ?string $comment = null,
|
||||
protected ?bool $unsigned = null,
|
||||
protected ?string $collate = null,
|
||||
protected ?int $srid = null,
|
||||
protected ?string $baseType = null,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the column name.
|
||||
*
|
||||
* @param string $name Name
|
||||
* @return $this
|
||||
*/
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the column name.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the base type if defined. Will fallback to `type` if not set.
|
||||
*
|
||||
* Used to get the base type of a column when the column type is a complex/custom type.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getBaseType(): ?string
|
||||
{
|
||||
if (isset($this->baseType)) {
|
||||
return $this->baseType;
|
||||
}
|
||||
$type = $this->type;
|
||||
if (TypeFactory::getMapped($type)) {
|
||||
$type = TypeFactory::build($type)->getBaseType();
|
||||
}
|
||||
|
||||
return $this->baseType = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the base type of the column.
|
||||
*
|
||||
* Used to set the base type of a column when the column type is a complex/custom type.
|
||||
*
|
||||
* @param string|null $baseType Base type
|
||||
* @return $this
|
||||
*/
|
||||
public function setBaseType(?string $baseType)
|
||||
{
|
||||
$this->baseType = $baseType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the column type.
|
||||
*
|
||||
* Type names are not validated, as drivers and dialects may implement
|
||||
* platform specific types that are not known by cakephp.
|
||||
*
|
||||
* Drivers are expected to handle unknown types gracefully.
|
||||
*
|
||||
* @param string $type Column type
|
||||
* @return $this
|
||||
*/
|
||||
public function setType(string $type)
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the column type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the column length.
|
||||
*
|
||||
* @param int|null $length Length
|
||||
* @return $this
|
||||
*/
|
||||
public function setLength(?int $length)
|
||||
{
|
||||
$this->length = $length;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the column length.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getLength(): ?int
|
||||
{
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the column allows nulls.
|
||||
*
|
||||
* @param bool $null Null
|
||||
* @return $this
|
||||
*/
|
||||
public function setNull(bool $null)
|
||||
{
|
||||
$this->null = $null;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the column allows nulls.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function getNull(): ?bool
|
||||
{
|
||||
return $this->null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the column allow nulls?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isNull(): bool
|
||||
{
|
||||
return $this->getNull() === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the default column value.
|
||||
*
|
||||
* @param mixed $default Default
|
||||
* @return $this
|
||||
*/
|
||||
public function setDefault(mixed $default)
|
||||
{
|
||||
$this->default = $default;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the default column value.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getDefault(): mixed
|
||||
{
|
||||
return $this->default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets generated option for identity columns. Ignored otherwise.
|
||||
*
|
||||
* @param string|null $generated Generated option
|
||||
* @return $this
|
||||
*/
|
||||
public function setGenerated(?string $generated)
|
||||
{
|
||||
$this->generated = $generated;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets generated option for identity columns. Null otherwise
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getGenerated(): ?string
|
||||
{
|
||||
return $this->generated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the column is an identity column.
|
||||
*
|
||||
* @param bool $identity Identity
|
||||
* @return $this
|
||||
*/
|
||||
public function setIdentity(bool $identity)
|
||||
{
|
||||
$this->identity = $identity;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the column is an identity column.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getIdentity(): bool
|
||||
{
|
||||
return $this->identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the column an identity column?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isIdentity(): bool
|
||||
{
|
||||
return $this->getIdentity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the name of the column to add this column after.
|
||||
*
|
||||
* @param string $after After
|
||||
* @return $this
|
||||
*/
|
||||
public function setAfter(string $after)
|
||||
{
|
||||
$this->after = $after;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the column to add this column after.
|
||||
*
|
||||
* Used by MySQL and MariaDB in ALTER TABLE statements.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getAfter(): ?string
|
||||
{
|
||||
return $this->after;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the 'ON UPDATE' mysql column function.
|
||||
*
|
||||
* Used by MySQL and MariaDB in ALTER TABLE statements.
|
||||
*
|
||||
* @param string $update On Update function
|
||||
* @return $this
|
||||
*/
|
||||
public function setOnUpdate(string $update)
|
||||
{
|
||||
$this->onUpdate = $update;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the ON UPDATE column function.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getOnUpdate(): ?string
|
||||
{
|
||||
return $this->onUpdate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the number precision for decimal or float column.
|
||||
*
|
||||
* For example `DECIMAL(5,2)`, 5 is the length and 2 is the precision,
|
||||
* and the column could store value from -999.99 to 999.99.
|
||||
*
|
||||
* @param int|null $precision Number precision
|
||||
* @return $this
|
||||
*/
|
||||
public function setPrecision(?int $precision)
|
||||
{
|
||||
$this->precision = $precision;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number precision for decimal or float column.
|
||||
*
|
||||
* For example `DECIMAL(5,2)`, 5 is the length and 2 is the precision,
|
||||
* and the column could store value from -999.99 to 999.99.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getPrecision(): ?int
|
||||
{
|
||||
return $this->precision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the column identity increment.
|
||||
*
|
||||
* @param int $increment Number increment
|
||||
* @return $this
|
||||
*/
|
||||
public function setIncrement(int $increment)
|
||||
{
|
||||
$this->increment = $increment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the column identity increment.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getIncrement(): ?int
|
||||
{
|
||||
return $this->increment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the column comment.
|
||||
*
|
||||
* @param string|null $comment Comment
|
||||
* @return $this
|
||||
*/
|
||||
public function setComment(?string $comment)
|
||||
{
|
||||
$this->comment = $comment;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the column comment.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getComment(): ?string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether field should be unsigned.
|
||||
*
|
||||
* @param bool $unsigned Signed
|
||||
* @return $this
|
||||
*/
|
||||
public function setUnsigned(bool $unsigned)
|
||||
{
|
||||
$this->unsigned = $unsigned;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether field should be unsigned.
|
||||
*
|
||||
* @return bool|null
|
||||
*/
|
||||
public function getUnsigned(): ?bool
|
||||
{
|
||||
return $this->unsigned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the column be signed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSigned(): bool
|
||||
{
|
||||
return !$this->getUnsigned();
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the column be unsigned?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isUnsigned(): bool
|
||||
{
|
||||
return $this->getUnsigned() === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the column collation.
|
||||
*
|
||||
* @param string $collation Collation
|
||||
* @return $this
|
||||
*/
|
||||
public function setCollate(string $collation)
|
||||
{
|
||||
$this->collate = $collation;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the column collation.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCollate(): ?string
|
||||
{
|
||||
return $this->collate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the column SRID for geometry fields.
|
||||
*
|
||||
* @param int $srid SRID
|
||||
* @return $this
|
||||
*/
|
||||
public function setSrid(int $srid)
|
||||
{
|
||||
$this->srid = $srid;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the column SRID from geometry fields.
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getSrid(): ?int
|
||||
{
|
||||
return $this->srid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all allowed options. Each option must have a corresponding `setFoo` method.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getValidOptions(): array
|
||||
{
|
||||
return [
|
||||
'name',
|
||||
'length',
|
||||
'precision',
|
||||
'default',
|
||||
'null',
|
||||
'identity',
|
||||
'after',
|
||||
'onUpdate',
|
||||
'comment',
|
||||
'unsigned',
|
||||
'type',
|
||||
'properties',
|
||||
'collate',
|
||||
'srid',
|
||||
'increment',
|
||||
'generated',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method that maps an array of column attributes to this object's methods.
|
||||
*
|
||||
* @param array<string, mixed> $attributes Attributes
|
||||
* @throws \RuntimeException
|
||||
* @return $this
|
||||
*/
|
||||
public function setAttributes(array $attributes)
|
||||
{
|
||||
$validOptions = $this->getValidOptions();
|
||||
if (isset($attributes['identity']) && $attributes['identity'] && !isset($attributes['null'])) {
|
||||
$attributes['null'] = false;
|
||||
}
|
||||
|
||||
foreach ($attributes as $attribute => $value) {
|
||||
if (!in_array($attribute, $validOptions, true)) {
|
||||
throw new RuntimeException(sprintf('"%s" is not a valid column option.', $attribute));
|
||||
}
|
||||
|
||||
$method = 'set' . ucfirst($attribute);
|
||||
$this->$method($value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an index into an array that is compatible with the Column constructor.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
$type = $this->getType();
|
||||
$length = $this->getLength();
|
||||
$precision = $this->getPrecision();
|
||||
if ($precision !== null && $precision > 0) {
|
||||
if ($type === TableSchemaInterface::TYPE_TIMESTAMP) {
|
||||
$type = 'timestampfractional';
|
||||
} elseif ($type === TableSchemaInterface::TYPE_DATETIME) {
|
||||
$type = 'datetimefractional';
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'name' => $this->getName(),
|
||||
'baseType' => $this->getBaseType(),
|
||||
'type' => $type,
|
||||
'length' => $length,
|
||||
'null' => $this->getNull(),
|
||||
'default' => $this->getDefault(),
|
||||
'generated' => $this->getGenerated(),
|
||||
'unsigned' => $this->getUnsigned(),
|
||||
'onUpdate' => $this->getOnUpdate(),
|
||||
'collate' => $this->getCollate(),
|
||||
'precision' => $precision,
|
||||
'srid' => $this->getSrid(),
|
||||
'comment' => $this->getComment(),
|
||||
'autoIncrement' => $this->getIdentity(),
|
||||
'identity' => $this->getIdentity(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc.
|
||||
* (https://github.com/cakephp/migrations/tree/master/LICENSE.txt)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 5.3.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Schema;
|
||||
|
||||
/**
|
||||
* Constraint base class object
|
||||
*
|
||||
* Models a database constraint like a unique or primary key.
|
||||
*/
|
||||
class Constraint
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const PRIMARY = TableSchema::CONSTRAINT_PRIMARY;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const UNIQUE = TableSchema::CONSTRAINT_UNIQUE;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const FOREIGN = TableSchema::CONSTRAINT_FOREIGN;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const CHECK = TableSchema::CONSTRAINT_CHECK;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $name The name of the constraint.
|
||||
* @param array<string> $columns The columns to constraint.
|
||||
* @param string $type The type of constraint, e.g. 'unique', 'primary'.
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $name,
|
||||
protected array $columns,
|
||||
protected string $type,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the constraint columns.
|
||||
*
|
||||
* @param array<string>|string $columns Columns
|
||||
* @return $this
|
||||
*/
|
||||
public function setColumns(string|array $columns)
|
||||
{
|
||||
$this->columns = (array)$columns;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constraint columns.
|
||||
*
|
||||
* @return ?array<string>
|
||||
*/
|
||||
public function getColumns(): ?array
|
||||
{
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the constraint type.
|
||||
*
|
||||
* @param string $type Type
|
||||
* @return $this
|
||||
*/
|
||||
public function setType(string $type)
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constraint type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the constraint name.
|
||||
*
|
||||
* @param string $name Name
|
||||
* @return $this
|
||||
*/
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constraint name.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a constraint to an array that is compatible
|
||||
* with the constructor.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'type' => $this->type,
|
||||
'columns' => $this->columns,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc.
|
||||
* (https://github.com/cakephp/migrations/tree/master/LICENSE.txt)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 5.3.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Schema;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* ForeignKey metadata object
|
||||
*
|
||||
* Models a database foreign key constraint
|
||||
*/
|
||||
class ForeignKey extends Constraint
|
||||
{
|
||||
public const CASCADE = 'cascade';
|
||||
public const RESTRICT = 'restrict';
|
||||
public const SET_NULL = 'setNull';
|
||||
public const NO_ACTION = 'noAction';
|
||||
public const SET_DEFAULT = 'setDefault';
|
||||
public const DEFERRED = 'DEFERRABLE INITIALLY DEFERRED';
|
||||
public const IMMEDIATE = 'DEFERRABLE INITIALLY IMMEDIATE';
|
||||
public const NOT_DEFERRED = 'NOT DEFERRABLE';
|
||||
|
||||
/**
|
||||
* An allow list of valid actions
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected array $validActions = [
|
||||
self::CASCADE,
|
||||
self::RESTRICT,
|
||||
self::SET_NULL,
|
||||
self::NO_ACTION,
|
||||
self::SET_DEFAULT,
|
||||
];
|
||||
|
||||
/**
|
||||
* The action to take when the referenced row is deleted.
|
||||
*/
|
||||
protected ?string $delete = null;
|
||||
|
||||
/**
|
||||
* The action to take when the referenced row is updated.
|
||||
*/
|
||||
protected ?string $update = null;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected ?string $deferrable = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $name The name of the index.
|
||||
* @param array<string> $columns The columns to index.
|
||||
* @param ?string $referencedTable The columns to index.
|
||||
* @param array<string> $referencedColumns The columns in $referencedTable that this key references.
|
||||
* @param ?string $delete The action to take when the referenced row is deleted.
|
||||
* @param ?string $update The action to take when the referenced row is updated.
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $name,
|
||||
protected array $columns,
|
||||
protected ?string $referencedTable = null,
|
||||
protected array $referencedColumns = [],
|
||||
?string $delete = null,
|
||||
?string $update = null,
|
||||
?string $deferrable = null,
|
||||
) {
|
||||
$this->type = self::FOREIGN;
|
||||
$this->delete = $this->normalizeAction($delete ?? self::NO_ACTION);
|
||||
$this->update = $this->normalizeAction($update ?? self::NO_ACTION);
|
||||
if ($deferrable) {
|
||||
$this->deferrable = $this->normalizeDeferrable($deferrable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the foreign key referenced table.
|
||||
*
|
||||
* @param string $table The table this KEY is pointing to
|
||||
* @return $this
|
||||
*/
|
||||
public function setReferencedTable(string $table)
|
||||
{
|
||||
$this->referencedTable = $table;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the foreign key referenced table.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getReferencedTable(): ?string
|
||||
{
|
||||
return $this->referencedTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the foreign key referenced columns.
|
||||
*
|
||||
* @param array<string>|string $referencedColumns Referenced columns
|
||||
* @return $this
|
||||
*/
|
||||
public function setReferencedColumns(array|string $referencedColumns)
|
||||
{
|
||||
$referencedColumns = is_string($referencedColumns) ? [$referencedColumns] : $referencedColumns;
|
||||
$this->referencedColumns = $referencedColumns;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the foreign key referenced columns.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function getReferencedColumns(): array
|
||||
{
|
||||
return $this->referencedColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the foreign key to an array that is compatible
|
||||
* with the constructor.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'type' => $this->type,
|
||||
'columns' => $this->columns,
|
||||
'referencedTable' => $this->referencedTable,
|
||||
'referencedColumns' => $this->referencedColumns,
|
||||
'delete' => $this->delete,
|
||||
'update' => $this->update,
|
||||
'deferrable' => $this->deferrable,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets ON DELETE action for the foreign key.
|
||||
*
|
||||
* @param string $delete On Delete
|
||||
* @return $this
|
||||
*/
|
||||
public function setDelete(string $delete)
|
||||
{
|
||||
$this->delete = $this->normalizeAction($delete);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets ON DELETE action for the foreign key.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getDelete(): ?string
|
||||
{
|
||||
return $this->delete;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets ON UPDATE action for the foreign key.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getUpdate(): ?string
|
||||
{
|
||||
return $this->update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets ON UPDATE action for the foreign key.
|
||||
*
|
||||
* @param string $update On Update
|
||||
* @return $this
|
||||
*/
|
||||
public function setUpdate(string $update)
|
||||
{
|
||||
$this->update = $this->normalizeAction($update);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* From passed value checks if it's correct and fixes if needed
|
||||
*
|
||||
* @param string $action Action
|
||||
* @throws \InvalidArgumentException
|
||||
* @return string
|
||||
*/
|
||||
protected function normalizeAction(string $action): string
|
||||
{
|
||||
if (in_array($action, $this->validActions, true)) {
|
||||
return $action;
|
||||
}
|
||||
throw new InvalidArgumentException('Unknown action passed: ' . $action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets deferrable mode for the foreign key.
|
||||
*
|
||||
* @param string $deferrable Constraint
|
||||
* @return $this
|
||||
*/
|
||||
public function setDeferrable(string $deferrable)
|
||||
{
|
||||
$this->deferrable = $this->normalizeDeferrable($deferrable);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets deferrable mode for the foreign key.
|
||||
*/
|
||||
public function getDeferrable(): ?string
|
||||
{
|
||||
return $this->deferrable;
|
||||
}
|
||||
|
||||
/**
|
||||
* From passed value checks if it's correct and fixes if needed
|
||||
*
|
||||
* @param string $deferrable Deferrable
|
||||
* @throws \InvalidArgumentException
|
||||
* @return string
|
||||
*/
|
||||
protected function normalizeDeferrable(string $deferrable): string
|
||||
{
|
||||
$mapping = [
|
||||
'DEFERRED' => ForeignKey::DEFERRED,
|
||||
'IMMEDIATE' => ForeignKey::IMMEDIATE,
|
||||
'NOT DEFERRED' => ForeignKey::NOT_DEFERRED,
|
||||
ForeignKey::DEFERRED => ForeignKey::DEFERRED,
|
||||
ForeignKey::IMMEDIATE => ForeignKey::IMMEDIATE,
|
||||
ForeignKey::NOT_DEFERRED => ForeignKey::NOT_DEFERRED,
|
||||
];
|
||||
$normalized = strtoupper(str_replace('_', ' ', $deferrable));
|
||||
if (array_key_exists($normalized, $mapping)) {
|
||||
return $mapping[$normalized];
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Unknown deferrable passed: ' . $deferrable);
|
||||
}
|
||||
}
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc.
|
||||
* (https://github.com/cakephp/migrations/tree/master/LICENSE.txt)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 5.3.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Schema;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Index value object
|
||||
*
|
||||
* Models a database index and its attributes.
|
||||
*/
|
||||
class Index
|
||||
{
|
||||
// TODO change the direction of these.
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const INDEX = 'index';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const FULLTEXT = 'fulltext';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $name The name of the index.
|
||||
* @param array<string> $columns The columns to index.
|
||||
* @param string $type The type of index, e.g. 'index', 'fulltext'.
|
||||
* @param array<string, int>|int|null $length The length of the index.
|
||||
* @param array<string>|null $order The sort order of the index columns.
|
||||
* @param array<string>|null $include The included columns for covering indexes.
|
||||
* @param ?string $where The where clause for partial indexes.
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $name,
|
||||
protected array $columns,
|
||||
protected string $type = self::INDEX,
|
||||
protected array|int|null $length = null,
|
||||
protected ?array $order = null,
|
||||
protected ?array $include = null,
|
||||
protected ?string $where = null,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index columns.
|
||||
*
|
||||
* @param array<string>|string $columns Columns
|
||||
* @return $this
|
||||
*/
|
||||
public function setColumns(string|array $columns)
|
||||
{
|
||||
$this->columns = (array)$columns;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index columns.
|
||||
*
|
||||
* @return ?array<string>
|
||||
*/
|
||||
public function getColumns(): ?array
|
||||
{
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index type.
|
||||
*
|
||||
* @param string $type Type
|
||||
* @return $this
|
||||
*/
|
||||
public function setType(string $type)
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index name.
|
||||
*
|
||||
* @param string $name Name
|
||||
* @return $this
|
||||
*/
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index name.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index length.
|
||||
*
|
||||
* In MySQL indexes can have limit clauses to control the number of
|
||||
* characters indexed in text and char columns.
|
||||
*
|
||||
* @param array<string, int>|int $length length value or array of length value
|
||||
* @return $this
|
||||
*/
|
||||
public function setLength(int|array $length)
|
||||
{
|
||||
$this->length = $length;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index length.
|
||||
*
|
||||
* Can be an array of column names and lengths under MySQL.
|
||||
*
|
||||
* @return array<string, int>|int|null
|
||||
*/
|
||||
public function getLength(): array|int|null
|
||||
{
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index columns sort order.
|
||||
*
|
||||
* @param array<string> $order column name sort order key value pair
|
||||
* @return $this
|
||||
*/
|
||||
public function setOrder(array $order)
|
||||
{
|
||||
$this->order = $order;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index columns sort order.
|
||||
*
|
||||
* @return ?array<string>
|
||||
*/
|
||||
public function getOrder(): ?array
|
||||
{
|
||||
return $this->order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index included columns for a 'covering index'.
|
||||
*
|
||||
* In postgres and sqlserver, indexes can define additional non-key
|
||||
* columns to build 'covering indexes'. This feature allows you to
|
||||
* further optimize well-crafted queries that leverage specific
|
||||
* indexes by reading all data from the index.
|
||||
*
|
||||
* @param array<string> $includedColumns Columns
|
||||
* @return $this
|
||||
*/
|
||||
public function setInclude(array $includedColumns)
|
||||
{
|
||||
$this->include = $includedColumns;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the index included columns.
|
||||
*
|
||||
* @return ?array<string>
|
||||
*/
|
||||
public function getInclude(): ?array
|
||||
{
|
||||
return $this->include;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the where clause for partial indexes.
|
||||
*
|
||||
* @param ?string $where The where clause for partial indexes.
|
||||
* @return $this
|
||||
*/
|
||||
public function setWhere(?string $where)
|
||||
{
|
||||
$this->where = $where;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the where clause for partial indexes.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getWhere(): ?string
|
||||
{
|
||||
return $this->where;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method that maps an array of index options to this object's methods.
|
||||
*
|
||||
* @param array<string, mixed> $attributes Attributes to set.
|
||||
* @throws \RuntimeException
|
||||
* @return $this
|
||||
*/
|
||||
public function setAttributes(array $attributes)
|
||||
{
|
||||
// Valid Options
|
||||
$validOptions = ['columns', 'type', 'name', 'length', 'order', 'include', 'where'];
|
||||
foreach ($attributes as $attr => $value) {
|
||||
if (!in_array($attr, $validOptions, true)) {
|
||||
throw new RuntimeException(sprintf('"%s" is not a valid index option.', $attr));
|
||||
}
|
||||
$method = 'set' . ucfirst($attr);
|
||||
$this->$method($value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an index into an array that is compatible with the Index constructor.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->getName(),
|
||||
'columns' => $this->getColumns(),
|
||||
'type' => $this->getType(),
|
||||
'length' => $this->getLength(),
|
||||
'order' => $this->getOrder(),
|
||||
'include' => $this->getInclude(),
|
||||
'where' => $this->getWhere(),
|
||||
];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,760 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Schema;
|
||||
|
||||
use Cake\Database\Driver;
|
||||
use Cake\Database\Exception\DatabaseException;
|
||||
use Cake\Database\Exception\QueryException;
|
||||
use Cake\Database\Type\ColumnSchemaAwareInterface;
|
||||
use Cake\Database\TypeFactory;
|
||||
use InvalidArgumentException;
|
||||
use PDOException;
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
/**
|
||||
* Base class for schema implementations.
|
||||
*
|
||||
* This class contains methods that are common across
|
||||
* the various SQL dialects.
|
||||
*
|
||||
* Provides methods for performing schema reflection. Results
|
||||
* will be in the form of structured arrays. The structure
|
||||
* of each result will be documented in this class. Subclasses
|
||||
* are free to include *additional* data that is not documented.
|
||||
*
|
||||
* @method array<mixed> listTablesWithoutViewsSql(array $config) Generate the SQL to list the tables, excluding all views.
|
||||
*/
|
||||
abstract class SchemaDialect
|
||||
{
|
||||
/**
|
||||
* The driver instance being used.
|
||||
*
|
||||
* @var \Cake\Database\Driver
|
||||
*/
|
||||
protected Driver $_driver;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* This constructor will connect the driver so that methods like columnSql() and others
|
||||
* will fail when the driver has not been connected.
|
||||
*
|
||||
* @param \Cake\Database\Driver $driver The driver to use.
|
||||
*/
|
||||
public function __construct(Driver $driver)
|
||||
{
|
||||
$driver->connect();
|
||||
$this->_driver = $driver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate an ON clause for a foreign key.
|
||||
*
|
||||
* @param string $on The on clause
|
||||
* @return string
|
||||
*/
|
||||
protected function _foreignOnClause(string $on): string
|
||||
{
|
||||
if ($on === TableSchema::ACTION_SET_NULL) {
|
||||
return 'SET NULL';
|
||||
}
|
||||
if ($on === TableSchema::ACTION_SET_DEFAULT) {
|
||||
return 'SET DEFAULT';
|
||||
}
|
||||
if ($on === TableSchema::ACTION_CASCADE) {
|
||||
return 'CASCADE';
|
||||
}
|
||||
if ($on === TableSchema::ACTION_RESTRICT) {
|
||||
return 'RESTRICT';
|
||||
}
|
||||
if ($on === TableSchema::ACTION_NO_ACTION) {
|
||||
return 'NO ACTION';
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Invalid value for "on": ' . $on);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert string on clauses to the abstract ones.
|
||||
*
|
||||
* @param string $clause The on clause to convert.
|
||||
* @return string
|
||||
*/
|
||||
protected function _convertOnClause(string $clause): string
|
||||
{
|
||||
if ($clause === 'CASCADE' || $clause === 'RESTRICT') {
|
||||
return strtolower($clause);
|
||||
}
|
||||
if ($clause === 'NO ACTION') {
|
||||
return TableSchema::ACTION_NO_ACTION;
|
||||
}
|
||||
|
||||
return TableSchema::ACTION_SET_NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert foreign key constraints references to a valid
|
||||
* stringified list
|
||||
*
|
||||
* @param array<string>|string $references The referenced columns of a foreign key constraint statement
|
||||
* @return string
|
||||
*/
|
||||
protected function _convertConstraintColumns(array|string $references): string
|
||||
{
|
||||
if (is_string($references)) {
|
||||
return $this->_driver->quoteIdentifier($references);
|
||||
}
|
||||
|
||||
return implode(', ', array_map(
|
||||
$this->_driver->quoteIdentifier(...),
|
||||
$references,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to use a matching database type to generate the SQL
|
||||
* fragment for a single column in a table.
|
||||
*
|
||||
* @param string $columnType The column type.
|
||||
* @param \Cake\Database\Schema\TableSchemaInterface $schema The table schema instance the column is in.
|
||||
* @param string $column The name of the column.
|
||||
* @return string|null An SQL fragment, or `null` in case no corresponding type was found or the type didn't provide
|
||||
* custom column SQL.
|
||||
*/
|
||||
protected function _getTypeSpecificColumnSql(
|
||||
string $columnType,
|
||||
TableSchemaInterface $schema,
|
||||
string $column,
|
||||
): ?string {
|
||||
if (!TypeFactory::getMapped($columnType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = TypeFactory::build($columnType);
|
||||
if (!($type instanceof ColumnSchemaAwareInterface)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $type->getColumnSql($schema, $column, $this->_driver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to use a matching database type to convert a SQL column
|
||||
* definition to an abstract type definition.
|
||||
*
|
||||
* @param string $columnType The column type.
|
||||
* @param array $definition The column definition.
|
||||
* @return array<string, mixed>|null Array of column information, or `null`
|
||||
* in case no corresponding type was found or the type didn't provide custom column information.
|
||||
*/
|
||||
protected function _applyTypeSpecificColumnConversion(string $columnType, array $definition): ?array
|
||||
{
|
||||
if (!TypeFactory::getMapped($columnType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$type = TypeFactory::build($columnType);
|
||||
if (!($type instanceof ColumnSchemaAwareInterface)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $type->convertColumnDefinition($definition, $this->_driver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SQL to drop a table.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema Schema instance
|
||||
* @return array SQL statements to drop a table.
|
||||
*/
|
||||
public function dropTableSql(TableSchema $schema): array
|
||||
{
|
||||
$sql = sprintf(
|
||||
'DROP TABLE %s',
|
||||
$this->_driver->quoteIdentifier($schema->name()),
|
||||
);
|
||||
|
||||
return [$sql];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SQL to list the tables.
|
||||
*
|
||||
* @param array<string, mixed> $config The connection configuration to use for
|
||||
* getting tables from.
|
||||
* @return array An array of (sql, params) to execute.
|
||||
* @deprecated 5.2.0 Use `listTables()` instead.
|
||||
*/
|
||||
abstract public function listTablesSql(array $config): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL to describe a table.
|
||||
*
|
||||
* @param string $tableName The table name to get information on.
|
||||
* @param array<string, mixed> $config The connection configuration.
|
||||
* @return array An array of (sql, params) to execute.
|
||||
* @deprecated 5.2.0 Use `describeColumns()` instead.
|
||||
*/
|
||||
abstract public function describeColumnSql(string $tableName, array $config): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL to describe the indexes in a table.
|
||||
*
|
||||
* @param string $tableName The table name to get information on.
|
||||
* @param array<string, mixed> $config The connection configuration.
|
||||
* @return array An array of (sql, params) to execute.
|
||||
* @deprecated 5.2.0 Use `describeIndexes()` instead.
|
||||
*/
|
||||
abstract public function describeIndexSql(string $tableName, array $config): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL to describe the foreign keys in a table.
|
||||
*
|
||||
* @param string $tableName The table name to get information on.
|
||||
* @param array<string, mixed> $config The connection configuration.
|
||||
* @return array An array of (sql, params) to execute.
|
||||
* @deprecated 5.2.0 Use `describeForeignKeys()` instead.
|
||||
*/
|
||||
abstract public function describeForeignKeySql(string $tableName, array $config): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL to describe table options
|
||||
*
|
||||
* @param string $tableName Table name.
|
||||
* @param array<string, mixed> $config The connection configuration.
|
||||
* @return array SQL statements to get options for a table.
|
||||
* @deprecated 5.2.0 Use `describeOptions()` instead.
|
||||
*/
|
||||
public function describeOptionsSql(string $tableName, array $config): array
|
||||
{
|
||||
return ['', ''];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert field description results into abstract schema fields.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table object to append fields to.
|
||||
* @param array $row The row data from `describeColumnSql`.
|
||||
* @return void
|
||||
* @deprecated 5.2.0 Use `describeColumns()` instead.
|
||||
*/
|
||||
abstract public function convertColumnDescription(TableSchema $schema, array $row): void;
|
||||
|
||||
/**
|
||||
* Convert an index description results into abstract schema indexes or constraints.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table object to append
|
||||
* an index or constraint to.
|
||||
* @param array $row The row data from `describeIndexSql`.
|
||||
* @return void
|
||||
* @deprecated 5.2.0 Use `describeIndexes()` instead.
|
||||
*/
|
||||
abstract public function convertIndexDescription(TableSchema $schema, array $row): void;
|
||||
|
||||
/**
|
||||
* Convert a foreign key description into constraints on the Table object.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table object to append
|
||||
* a constraint to.
|
||||
* @param array $row The row data from `describeForeignKeySql`.
|
||||
* @return void
|
||||
* @deprecated 5.2.0 Use `describeForeignKeys()` instead.
|
||||
*/
|
||||
abstract public function convertForeignKeyDescription(TableSchema $schema, array $row): void;
|
||||
|
||||
/**
|
||||
* Convert options data into table options.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema Table instance.
|
||||
* @param array $row The row of data.
|
||||
* @return void
|
||||
* @deprecated 5.2.0 Use `describeOptions()` instead.
|
||||
*/
|
||||
public function convertOptionsDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SQL to create a table.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema Table instance.
|
||||
* @param array<string> $columns The columns to go inside the table.
|
||||
* @param array<string> $constraints The constraints for the table.
|
||||
* @param array<string> $indexes The indexes for the table.
|
||||
* @return array<string> SQL statements to create a table.
|
||||
*/
|
||||
abstract public function createTableSql(
|
||||
TableSchema $schema,
|
||||
array $columns,
|
||||
array $constraints,
|
||||
array $indexes,
|
||||
): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL fragment for a single column in a table.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table instance the column is in.
|
||||
* @param string $name The name of the column.
|
||||
* @return string SQL fragment.
|
||||
*/
|
||||
abstract public function columnSql(TableSchema $schema, string $name): string;
|
||||
|
||||
/**
|
||||
* Generate the SQL queries needed to add foreign key constraints to the table
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table instance the foreign key constraints are.
|
||||
* @return array SQL fragment.
|
||||
*/
|
||||
abstract public function addConstraintSql(TableSchema $schema): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL queries needed to drop foreign key constraints from the table
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table instance the foreign key constraints are.
|
||||
* @return array SQL fragment.
|
||||
*/
|
||||
abstract public function dropConstraintSql(TableSchema $schema): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL fragments for defining table constraints.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table instance the column is in.
|
||||
* @param string $name The name of the column.
|
||||
* @return string SQL fragment.
|
||||
*/
|
||||
abstract public function constraintSql(TableSchema $schema, string $name): string;
|
||||
|
||||
/**
|
||||
* Generate the SQL fragment for a single index in a table.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table object the column is in.
|
||||
* @param string $name The name of the column.
|
||||
* @return string SQL fragment.
|
||||
*/
|
||||
abstract public function indexSql(TableSchema $schema, string $name): string;
|
||||
|
||||
/**
|
||||
* Generate the SQL to truncate a table.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema Table instance.
|
||||
* @return array SQL statements to truncate a table.
|
||||
*/
|
||||
abstract public function truncateTableSql(TableSchema $schema): array;
|
||||
|
||||
/**
|
||||
* Create a SQL snippet for a column based on the array shape
|
||||
* that `describeColumns()` creates.
|
||||
*
|
||||
* @param array $column The column metadata
|
||||
* @return string Generated SQL fragment for a column
|
||||
*/
|
||||
public function columnDefinitionSql(array $column): string
|
||||
{
|
||||
deprecationWarning(
|
||||
'5.2.0',
|
||||
'SchemaDialect subclasses need to implement `columnDefinitionSql` before 6.0.0',
|
||||
);
|
||||
$table = new TableSchema('placeholder');
|
||||
$table->addColumn($column['name'], $column);
|
||||
|
||||
return $this->columnSql($table, $column['name']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of tables, excluding any views, available in the current connection.
|
||||
*
|
||||
* @return array<string> The list of tables in the connected database/schema.
|
||||
*/
|
||||
public function listTablesWithoutViews(): array
|
||||
{
|
||||
[$sql, $params] = $this->listTablesWithoutViewsSql($this->_driver->config());
|
||||
$result = [];
|
||||
$statement = $this->_driver->execute($sql, $params);
|
||||
while ($row = $statement->fetch()) {
|
||||
$result[] = $row[0];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of tables and views available in the current connection.
|
||||
*
|
||||
* @param string|null $schema The schema to get the tables for. If null the default schema is used.
|
||||
* @return array<string> The list of tables and views in the connected database/schema.
|
||||
*/
|
||||
public function listTables(?string $schema = null): array
|
||||
{
|
||||
$config = $this->_driver->config();
|
||||
if ($schema !== null) {
|
||||
$config['schema'] = $schema;
|
||||
// Set database for MySQL
|
||||
$config['database'] = $schema;
|
||||
}
|
||||
[$sql, $params] = $this->listTablesSql($config);
|
||||
$result = [];
|
||||
$statement = $this->_driver->execute($sql, $params);
|
||||
while ($row = $statement->fetch()) {
|
||||
$result[] = $row[0];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the column metadata for a table.
|
||||
*
|
||||
* The name can include a database schema name in the form 'schema.table'.
|
||||
*
|
||||
* @param string $name The name of the table to describe.
|
||||
* @return \Cake\Database\Schema\TableSchemaInterface Object with column metadata.
|
||||
* @throws \Cake\Database\Exception\DatabaseException when table cannot be described.
|
||||
*/
|
||||
public function describe(string $name): TableSchemaInterface
|
||||
{
|
||||
$tableName = $name;
|
||||
if (str_contains($name, '.')) {
|
||||
$tableName = explode('.', $name)[1];
|
||||
}
|
||||
$table = $this->_driver->newTableSchema($tableName);
|
||||
foreach ($this->describeColumns($name) as $column) {
|
||||
$table->addColumn($column['name'], $column);
|
||||
}
|
||||
foreach ($this->describeIndexes($name) as $index) {
|
||||
if (in_array($index['type'], [TableSchema::CONSTRAINT_UNIQUE, TableSchema::CONSTRAINT_PRIMARY])) {
|
||||
$table->addConstraint($index['name'], $index);
|
||||
} else {
|
||||
$table->addIndex($index['name'], $index);
|
||||
}
|
||||
}
|
||||
foreach ($this->describeForeignKeys($name) as $key) {
|
||||
$table->addConstraint($key['name'], $key);
|
||||
}
|
||||
foreach ($this->describeCheckConstraints($name) as $key) {
|
||||
$table->addConstraint($key['name'], $key);
|
||||
}
|
||||
$options = $this->describeOptions($name);
|
||||
if ($options) {
|
||||
$table->setOptions($options);
|
||||
}
|
||||
if ($table->columns() === []) {
|
||||
throw new DatabaseException(sprintf('Cannot describe %s. It has 0 columns.', $name));
|
||||
}
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of column metadata as a array
|
||||
*
|
||||
* Each item in the array will contain the following:
|
||||
*
|
||||
* - name : the name of the column.
|
||||
* - type : the abstract type of the column.
|
||||
* - length : the length of the column.
|
||||
* - default : the default value of the column or null.
|
||||
* - null : boolean indicating whether the column can be null.
|
||||
* - comment : the column comment or null.
|
||||
*
|
||||
* Additionaly the `autoIncrement` key will be set for columns that are a primary key.
|
||||
*
|
||||
* @param string $tableName The name of the table to describe columns on.
|
||||
* @return array
|
||||
*/
|
||||
public function describeColumns(string $tableName): array
|
||||
{
|
||||
deprecationWarning(
|
||||
'5.2.0',
|
||||
'SchemaDialect subclasses need to implement `describeColumns` before 6.0.0',
|
||||
);
|
||||
$config = $this->_driver->config();
|
||||
if (str_contains($tableName, '.')) {
|
||||
[$config['schema'], $tableName] = explode('.', $tableName);
|
||||
}
|
||||
/** @var \Cake\Database\Schema\TableSchema $table */
|
||||
$table = $this->_driver->newTableSchema($tableName);
|
||||
|
||||
[$sql, $params] = $this->describeColumnSql($tableName, $config);
|
||||
$statement = $this->_driver->execute($sql, $params);
|
||||
foreach ($statement->fetchAll('assoc') as $row) {
|
||||
$this->convertColumnDescription($table, $row);
|
||||
}
|
||||
$columns = [];
|
||||
foreach ($table->columns() as $columnName) {
|
||||
$column = $table->getColumn($columnName);
|
||||
$column['name'] = $columnName;
|
||||
$columns[] = $column;
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of constraint metadata as a array
|
||||
*
|
||||
* Each item in the array will contain the following:
|
||||
*
|
||||
* - name : The name of the constraint
|
||||
* - type : the type of the constraint. Generally `foreign`.
|
||||
* - columns : the columns in the constraint on the.
|
||||
* - references : A list of the table + all columns in the referenced table
|
||||
* - update : The update action or null
|
||||
* - delete : The delete action or null
|
||||
*
|
||||
* @param string $tableName The name of the table to describe foreign keys on.
|
||||
* @return array
|
||||
*/
|
||||
public function describeForeignKeys(string $tableName): array
|
||||
{
|
||||
deprecationWarning(
|
||||
'5.2.0',
|
||||
'SchemaDialect subclasses need to implement `describeForeignKeys` before 6.0.0',
|
||||
);
|
||||
$config = $this->_driver->config();
|
||||
if (str_contains($tableName, '.')) {
|
||||
[$config['schema'], $tableName] = explode('.', $tableName);
|
||||
}
|
||||
/** @var \Cake\Database\Schema\TableSchema $table */
|
||||
$table = $this->_driver->newTableSchema($tableName);
|
||||
// Add the columns because TableSchema needs them.
|
||||
foreach ($this->describeColumns($tableName) as $column) {
|
||||
$table->addColumn($column['name'], $column);
|
||||
}
|
||||
|
||||
[$sql, $params] = $this->describeForeignKeySql($tableName, $config);
|
||||
$statement = $this->_driver->execute($sql, $params);
|
||||
foreach ($statement->fetchAll('assoc') as $row) {
|
||||
$this->convertForeignKeyDescription($table, $row);
|
||||
}
|
||||
$keys = [];
|
||||
foreach ($table->constraints() as $name) {
|
||||
$key = $table->getConstraint($name);
|
||||
$key['name'] = $name;
|
||||
$keys[] = $key;
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of index metadata as a array
|
||||
*
|
||||
* Each item in the array will contain the following:
|
||||
*
|
||||
* - name : the name of the index.
|
||||
* - type : the type of the index. One of `unique`, `index`, `primary`.
|
||||
* - columns : the columns in the index.
|
||||
* - length : the length of the index if applicable.
|
||||
*
|
||||
* @param string $tableName The name of the table to describe indexes on.
|
||||
* @return array
|
||||
*/
|
||||
public function describeIndexes(string $tableName): array
|
||||
{
|
||||
deprecationWarning(
|
||||
'5.2.0',
|
||||
'SchemaDialect subclasses need to implement `describeIndexes` before 6.0.0',
|
||||
);
|
||||
$config = $this->_driver->config();
|
||||
if (str_contains($tableName, '.')) {
|
||||
[$config['schema'], $tableName] = explode('.', $tableName);
|
||||
}
|
||||
/** @var \Cake\Database\Schema\TableSchema $table */
|
||||
$table = $this->_driver->newTableSchema($tableName);
|
||||
// Add the columns because TableSchema needs them.
|
||||
foreach ($this->describeColumns($tableName) as $column) {
|
||||
$table->addColumn($column['name'], $column);
|
||||
}
|
||||
|
||||
[$sql, $params] = $this->describeIndexSql($tableName, $config);
|
||||
$statement = $this->_driver->execute($sql, $params);
|
||||
foreach ($statement->fetchAll('assoc') as $row) {
|
||||
$this->convertIndexDescription($table, $row);
|
||||
}
|
||||
$indexes = [];
|
||||
foreach ($table->indexes() as $name) {
|
||||
$index = $table->getIndex($name);
|
||||
$index['name'] = $name;
|
||||
$indexes[] = $index;
|
||||
}
|
||||
|
||||
return $indexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get platform specific options
|
||||
*
|
||||
* No keys are guaranteed to be present as they are database driver dependent.
|
||||
*
|
||||
* @param string $tableName The name of the table to describe options on.
|
||||
* @return array
|
||||
*/
|
||||
public function describeOptions(string $tableName): array
|
||||
{
|
||||
deprecationWarning(
|
||||
'5.2.0',
|
||||
'SchemaDialect subclasses need to implement `describeOptions` before 6.0.0',
|
||||
);
|
||||
$config = $this->_driver->config();
|
||||
if (str_contains($tableName, '.')) {
|
||||
[$config['schema'], $tableName] = explode('.', $tableName);
|
||||
}
|
||||
/** @var \Cake\Database\Schema\TableSchema $table */
|
||||
$table = $this->_driver->newTableSchema($tableName);
|
||||
|
||||
[$sql, $params] = $this->describeOptionsSql($tableName, $config);
|
||||
if ($sql) {
|
||||
$statement = $this->_driver->execute($sql, $params);
|
||||
foreach ($statement->fetchAll('assoc') as $row) {
|
||||
$this->convertOptionsDescription($table, $row);
|
||||
}
|
||||
}
|
||||
|
||||
return $table->getOptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of check constraint metadata as an array.
|
||||
*
|
||||
* Each item in the array will contain the following keys:
|
||||
*
|
||||
* - name - The name of the constraint.
|
||||
* - expression - The check constraint expression as a SQL fragment.
|
||||
*
|
||||
* @param string $tableName The name of the table to describe options on.
|
||||
* @return array
|
||||
*/
|
||||
public function describeCheckConstraints(string $tableName): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a table has a column with a given name.
|
||||
*
|
||||
* @param string $tableName The name of the table
|
||||
* @param string $columnName The name of the column
|
||||
* @return bool
|
||||
*/
|
||||
public function hasColumn(string $tableName, string $columnName): bool
|
||||
{
|
||||
try {
|
||||
$columns = $this->describeColumns($tableName);
|
||||
} catch (PDOException | DatabaseException) {
|
||||
return false;
|
||||
}
|
||||
foreach ($columns as $column) {
|
||||
if ($column['name'] === $columnName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a table exists
|
||||
*
|
||||
* @param string $tableName The name of the table
|
||||
* @param string|null $schema The schema look for table in. If null the default schema is used.
|
||||
* @return bool
|
||||
*/
|
||||
public function hasTable(string $tableName, ?string $schema = null): bool
|
||||
{
|
||||
$tables = $this->listTables($schema);
|
||||
|
||||
return in_array($tableName, $tables, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a table has an index with a given name.
|
||||
*
|
||||
* @param string $tableName The name of the table
|
||||
* @param array<string> $columns The columns in the index. Specific
|
||||
* ordering matters.
|
||||
* @param string $name The name of the index to match on. Can be used alone,
|
||||
* or with $columns to match indexes more precisely.
|
||||
* @return bool
|
||||
*/
|
||||
public function hasIndex(string $tableName, array $columns = [], ?string $name = null): bool
|
||||
{
|
||||
try {
|
||||
$indexes = $this->describeIndexes($tableName);
|
||||
} catch (QueryException) {
|
||||
return false;
|
||||
}
|
||||
$found = null;
|
||||
foreach ($indexes as $index) {
|
||||
if ($columns && $index['columns'] === $columns) {
|
||||
$found = $index;
|
||||
break;
|
||||
}
|
||||
if ($columns === [] && $name !== null) {
|
||||
if ($index['name'] === $name) {
|
||||
$found = $index;
|
||||
break;
|
||||
}
|
||||
if (isset($index['constraint']) && $index['constraint'] === $name) {
|
||||
$found = $index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Both columns and name provided, both must match;
|
||||
if ($columns && $found && $name !== null && $found['name'] !== $name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $found !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a table has a foreign key with a given name.
|
||||
*
|
||||
* @param string $tableName The name of the table
|
||||
* @param array<string> $columns The columns in the foriegn key. Specific
|
||||
* ordering matters.
|
||||
* @param string $name The name of the foreign key to match on. Can be used alone,
|
||||
* or with $columns to match keys more precisely.
|
||||
* @return bool
|
||||
*/
|
||||
public function hasForeignKey(string $tableName, array $columns = [], ?string $name = null): bool
|
||||
{
|
||||
try {
|
||||
$keys = $this->describeForeignKeys($tableName);
|
||||
} catch (QueryException) {
|
||||
return false;
|
||||
}
|
||||
$found = null;
|
||||
foreach ($keys as $key) {
|
||||
if ($columns && $key['columns'] === $columns) {
|
||||
$found = $key;
|
||||
break;
|
||||
}
|
||||
if (!$columns && $name !== null && $key['name'] === $name) {
|
||||
$found = $key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Both columns and name provided, both must match;
|
||||
if ($found !== null && $name !== null && $found['name'] !== $name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $found !== null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.5.0
|
||||
* @license https://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Schema;
|
||||
|
||||
use Cake\Database\Connection;
|
||||
|
||||
/**
|
||||
* An interface used by TableSchema objects.
|
||||
*/
|
||||
interface SqlGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* Generate the SQL to create the Table.
|
||||
*
|
||||
* Uses the connection to access the schema dialect
|
||||
* to generate platform specific SQL.
|
||||
*
|
||||
* @param \Cake\Database\Connection $connection The connection to generate SQL for.
|
||||
* @return array List of SQL statements to create the table and the
|
||||
* required indexes.
|
||||
*/
|
||||
public function createSql(Connection $connection): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL to drop a table.
|
||||
*
|
||||
* Uses the connection to access the schema dialect to generate platform
|
||||
* specific SQL.
|
||||
*
|
||||
* @param \Cake\Database\Connection $connection The connection to generate SQL for.
|
||||
* @return array SQL to drop a table.
|
||||
*/
|
||||
public function dropSql(Connection $connection): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL statements to truncate a table
|
||||
*
|
||||
* @param \Cake\Database\Connection $connection The connection to generate SQL for.
|
||||
* @return array SQL to truncate a table.
|
||||
*/
|
||||
public function truncateSql(Connection $connection): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL statements to add the constraints to the table
|
||||
*
|
||||
* @param \Cake\Database\Connection $connection The connection to generate SQL for.
|
||||
* @return array SQL to add the constraints.
|
||||
*/
|
||||
public function addConstraintSql(Connection $connection): array;
|
||||
|
||||
/**
|
||||
* Generate the SQL statements to drop the constraints to the table
|
||||
*
|
||||
* @param \Cake\Database\Connection $connection The connection to generate SQL for.
|
||||
* @return array SQL to drop a table.
|
||||
*/
|
||||
public function dropConstraintSql(Connection $connection): array;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,971 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Schema;
|
||||
|
||||
/**
|
||||
* Schema management/reflection features for SQLServer.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class SqlserverSchemaDialect extends SchemaDialect
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const DEFAULT_SCHEMA_NAME = 'dbo';
|
||||
|
||||
/**
|
||||
* Generate the SQL to list the tables and views.
|
||||
*
|
||||
* @param array<string, mixed> $config The connection configuration to use for
|
||||
* getting tables from.
|
||||
* @return array An array of (sql, params) to execute.
|
||||
*/
|
||||
public function listTablesSql(array $config): array
|
||||
{
|
||||
$sql = "SELECT TABLE_NAME
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_SCHEMA = ?
|
||||
AND (TABLE_TYPE = 'BASE TABLE' OR TABLE_TYPE = 'VIEW')
|
||||
ORDER BY TABLE_NAME";
|
||||
$schema = $config['schema'] ?? static::DEFAULT_SCHEMA_NAME;
|
||||
|
||||
return [$sql, [$schema]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SQL to list the tables, excluding all views.
|
||||
*
|
||||
* @param array<string, mixed> $config The connection configuration to use for
|
||||
* getting tables from.
|
||||
* @return array<mixed> An array of (sql, params) to execute.
|
||||
*/
|
||||
public function listTablesWithoutViewsSql(array $config): array
|
||||
{
|
||||
$sql = "SELECT TABLE_NAME
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE TABLE_SCHEMA = ?
|
||||
AND (TABLE_TYPE = 'BASE TABLE')
|
||||
ORDER BY TABLE_NAME";
|
||||
$schema = $config['schema'] ?? static::DEFAULT_SCHEMA_NAME;
|
||||
|
||||
return [$sql, [$schema]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeColumnSql(string $tableName, array $config): array
|
||||
{
|
||||
$sql = $this->describeColumnQuery();
|
||||
$schema = $config['schema'] ?? static::DEFAULT_SCHEMA_NAME;
|
||||
|
||||
return [$sql, [$tableName, $schema]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for creating SQL to describe columns in a table.
|
||||
*
|
||||
* @return string SQL to reflect columns
|
||||
*/
|
||||
private function describeColumnQuery(): string
|
||||
{
|
||||
return 'SELECT DISTINCT
|
||||
AC.column_id AS [column_id],
|
||||
AC.name AS [name],
|
||||
TY.name AS [type],
|
||||
AC.max_length AS [char_length],
|
||||
AC.precision AS [precision],
|
||||
AC.scale AS [scale],
|
||||
AC.is_identity AS [autoincrement],
|
||||
AC.is_nullable AS [null],
|
||||
OBJECT_DEFINITION(AC.default_object_id) AS [default],
|
||||
AC.collation_name AS [collation_name],
|
||||
EP.[value] AS [comment]
|
||||
FROM sys.[objects] T
|
||||
INNER JOIN sys.[schemas] S ON S.[schema_id] = T.[schema_id]
|
||||
INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id]
|
||||
INNER JOIN sys.[types] TY ON TY.[user_type_id] = AC.[user_type_id]
|
||||
LEFT JOIN sys.[extended_properties] as EP
|
||||
ON T.[object_id] = EP.[major_id]
|
||||
AND AC.[column_id] = EP.[minor_id]
|
||||
AND EP.[name] = \'MS_Description\'
|
||||
WHERE T.[name] = ? AND S.[name] = ?
|
||||
ORDER BY column_id';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a column definition to the abstract types.
|
||||
*
|
||||
* The returned type will be a type that
|
||||
* Cake\Database\TypeFactory can handle.
|
||||
*
|
||||
* @param string $col The column type
|
||||
* @param int|null $length the column length
|
||||
* @param int|null $precision The column precision
|
||||
* @param int|null $scale The column scale
|
||||
* @return array<string, mixed> Array of column information.
|
||||
* @link https://technet.microsoft.com/en-us/library/ms187752.aspx
|
||||
*/
|
||||
protected function _convertColumn(
|
||||
string $col,
|
||||
?int $length = null,
|
||||
?int $precision = null,
|
||||
?int $scale = null,
|
||||
): array {
|
||||
$col = strtolower($col);
|
||||
|
||||
$type = $this->_applyTypeSpecificColumnConversion(
|
||||
$col,
|
||||
compact('length', 'precision', 'scale'),
|
||||
);
|
||||
if ($type !== null) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
if (in_array($col, ['date', 'time'])) {
|
||||
return ['type' => $col, 'length' => null];
|
||||
}
|
||||
|
||||
if ($col === 'datetime') {
|
||||
// datetime cannot parse more than 3 digits of precision and isn't accurate
|
||||
return ['type' => TableSchemaInterface::TYPE_DATETIME, 'length' => null];
|
||||
}
|
||||
if (str_contains($col, 'datetime')) {
|
||||
$typeName = TableSchemaInterface::TYPE_DATETIME;
|
||||
if ($scale > 0) {
|
||||
$typeName = TableSchemaInterface::TYPE_DATETIME_FRACTIONAL;
|
||||
}
|
||||
|
||||
return ['type' => $typeName, 'length' => null, 'precision' => $scale];
|
||||
}
|
||||
|
||||
if ($col === 'char') {
|
||||
return ['type' => TableSchemaInterface::TYPE_CHAR, 'length' => $length];
|
||||
}
|
||||
|
||||
if ($col === 'tinyint') {
|
||||
return ['type' => TableSchemaInterface::TYPE_TINYINTEGER, 'length' => $precision ?: 3];
|
||||
}
|
||||
if ($col === 'smallint') {
|
||||
return ['type' => TableSchemaInterface::TYPE_SMALLINTEGER, 'length' => $precision ?: 5];
|
||||
}
|
||||
if ($col === 'int' || $col === 'integer') {
|
||||
return ['type' => TableSchemaInterface::TYPE_INTEGER, 'length' => $precision ?: 10];
|
||||
}
|
||||
if ($col === 'bigint') {
|
||||
return ['type' => TableSchemaInterface::TYPE_BIGINTEGER, 'length' => $precision ?: 20];
|
||||
}
|
||||
if ($col === 'bit') {
|
||||
return ['type' => TableSchemaInterface::TYPE_BOOLEAN, 'length' => null];
|
||||
}
|
||||
if (
|
||||
str_contains($col, 'numeric') ||
|
||||
str_contains($col, 'money') ||
|
||||
str_contains($col, 'decimal')
|
||||
) {
|
||||
return ['type' => TableSchemaInterface::TYPE_DECIMAL, 'length' => $precision, 'precision' => $scale];
|
||||
}
|
||||
|
||||
if ($col === 'real' || $col === 'float') {
|
||||
return ['type' => TableSchemaInterface::TYPE_FLOAT, 'length' => null];
|
||||
}
|
||||
// SqlServer schema reflection returns double length for unicode
|
||||
// columns because internally it uses UTF16/UCS2
|
||||
if (in_array($col, ['nvarchar', 'nchar', 'ntext'], true)) {
|
||||
$length /= 2;
|
||||
}
|
||||
if (str_contains($col, 'varchar') && $length < 0) {
|
||||
return ['type' => TableSchemaInterface::TYPE_TEXT, 'length' => null];
|
||||
}
|
||||
|
||||
if (str_contains($col, 'varchar')) {
|
||||
return ['type' => TableSchemaInterface::TYPE_STRING, 'length' => $length ?: 255];
|
||||
}
|
||||
|
||||
if (str_contains($col, 'char')) {
|
||||
return ['type' => TableSchemaInterface::TYPE_CHAR, 'length' => $length];
|
||||
}
|
||||
|
||||
if (str_contains($col, 'text')) {
|
||||
return ['type' => TableSchemaInterface::TYPE_TEXT, 'length' => null];
|
||||
}
|
||||
|
||||
if ($col === 'image' || str_contains($col, 'binary')) {
|
||||
// -1 is the value for MAX which we treat as a 'long' binary
|
||||
if ($length == -1) {
|
||||
$length = TableSchema::LENGTH_LONG;
|
||||
}
|
||||
|
||||
return ['type' => TableSchemaInterface::TYPE_BINARY, 'length' => $length];
|
||||
}
|
||||
|
||||
if ($col === 'uniqueidentifier') {
|
||||
return ['type' => TableSchemaInterface::TYPE_UUID];
|
||||
}
|
||||
if ($col === 'geometry') {
|
||||
return ['type' => TableSchemaInterface::TYPE_GEOMETRY];
|
||||
}
|
||||
if ($col === 'geography') {
|
||||
// SQLserver only has one generic geometry type that
|
||||
// we map to point.
|
||||
return ['type' => TableSchemaInterface::TYPE_POINT];
|
||||
}
|
||||
|
||||
return ['type' => TableSchemaInterface::TYPE_STRING, 'length' => null];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertColumnDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$field = $this->_convertColumn(
|
||||
$row['type'],
|
||||
$row['char_length'] !== null ? (int)$row['char_length'] : null,
|
||||
$row['precision'] !== null ? (int)$row['precision'] : null,
|
||||
$row['scale'] !== null ? (int)$row['scale'] : null,
|
||||
);
|
||||
|
||||
if (!empty($row['autoincrement'])) {
|
||||
$field['autoIncrement'] = true;
|
||||
}
|
||||
|
||||
$field += [
|
||||
'null' => $row['null'] === '1',
|
||||
'default' => $this->_defaultValue($field['type'], $row['default']),
|
||||
'collate' => $row['collation_name'],
|
||||
];
|
||||
$schema->addColumn($row['name'], $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Split a tablename into a tuple of schema, table
|
||||
* If the table does not have a schema name included, the connection
|
||||
* schema will be used.
|
||||
*
|
||||
* @param string $tableName The table name to split
|
||||
* @return array A tuple of [schema, tablename]
|
||||
*/
|
||||
private function splitTablename(string $tableName): array
|
||||
{
|
||||
$config = $this->_driver->config();
|
||||
$schema = $config['schema'] ?? static::DEFAULT_SCHEMA_NAME;
|
||||
if (str_contains($tableName, '.')) {
|
||||
return explode('.', $tableName);
|
||||
}
|
||||
|
||||
return [$schema, $tableName];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeColumns(string $tableName): array
|
||||
{
|
||||
[$schema, $name] = $this->splitTablename($tableName);
|
||||
|
||||
$sql = $this->describeColumnQuery();
|
||||
$statement = $this->_driver->execute($sql, [$name, $schema]);
|
||||
$columns = [];
|
||||
foreach ($statement->fetchAll('assoc') as $row) {
|
||||
$field = $this->_convertColumn(
|
||||
$row['type'],
|
||||
$row['char_length'] !== null ? (int)$row['char_length'] : null,
|
||||
$row['precision'] !== null ? (int)$row['precision'] : null,
|
||||
$row['scale'] !== null ? (int)$row['scale'] : null,
|
||||
);
|
||||
|
||||
if (!empty($row['autoincrement'])) {
|
||||
$field['autoIncrement'] = true;
|
||||
}
|
||||
|
||||
$field += [
|
||||
'name' => $row['name'],
|
||||
'null' => $row['null'] === '1',
|
||||
'default' => $this->_defaultValue($field['type'], $row['default']),
|
||||
'comment' => $row['comment'] ?? null,
|
||||
'collate' => $row['collation_name'],
|
||||
];
|
||||
$columns[] = $field;
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Manipulate the default value.
|
||||
*
|
||||
* Removes () wrapping default values, extracts strings from
|
||||
* N'' wrappers and collation text and converts NULL strings.
|
||||
*
|
||||
* @param string $type The schema type
|
||||
* @param string|null $default The default value.
|
||||
* @return string|int|null
|
||||
*/
|
||||
protected function _defaultValue(string $type, ?string $default): string|int|null
|
||||
{
|
||||
if ($default === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// remove () surrounding value (NULL) but leave () at the end of functions
|
||||
// integers might have two ((0)) wrapping value
|
||||
if (preg_match('/^\(+(.*?(\(\))?)\)+$/', $default, $matches)) {
|
||||
$default = $matches[1];
|
||||
}
|
||||
|
||||
if ($default === 'NULL') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($type === TableSchemaInterface::TYPE_BOOLEAN) {
|
||||
return (int)$default;
|
||||
}
|
||||
|
||||
// Remove quotes
|
||||
if (preg_match("/^\(?N?'(.*)'\)?/", $default, $matches)) {
|
||||
return str_replace("''", "'", $matches[1]);
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeIndexSql(string $tableName, array $config): array
|
||||
{
|
||||
$sql = $this->describeIndexQuery();
|
||||
$schema = $config['schema'] ?? static::DEFAULT_SCHEMA_NAME;
|
||||
|
||||
return [$sql, [$tableName, $schema]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query to describe indexes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function describeIndexQuery(): string
|
||||
{
|
||||
return "SELECT
|
||||
I.[name] AS [index_name],
|
||||
IC.[index_column_id] AS [index_order],
|
||||
AC.[name] AS [column_name],
|
||||
I.[is_unique], I.[is_primary_key],
|
||||
I.[is_unique_constraint],
|
||||
IC.[is_included_column]
|
||||
FROM sys.[tables] AS T
|
||||
INNER JOIN sys.[schemas] S ON S.[schema_id] = T.[schema_id]
|
||||
INNER JOIN sys.[indexes] I ON T.[object_id] = I.[object_id]
|
||||
INNER JOIN sys.[index_columns] IC ON I.[object_id] = IC.[object_id] AND I.[index_id] = IC.[index_id]
|
||||
INNER JOIN sys.[all_columns] AC ON T.[object_id] = AC.[object_id] AND IC.[column_id] = AC.[column_id]
|
||||
WHERE T.[is_ms_shipped] = 0 AND I.[type_desc] <> 'HEAP' AND T.[name] = ? AND S.[name] = ?
|
||||
ORDER BY I.[index_id], IC.[index_column_id]";
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertIndexDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$type = TableSchema::INDEX_INDEX;
|
||||
$name = $row['index_name'];
|
||||
if ($row['is_primary_key']) {
|
||||
$name = TableSchema::CONSTRAINT_PRIMARY;
|
||||
$type = TableSchema::CONSTRAINT_PRIMARY;
|
||||
}
|
||||
if (($row['is_unique'] || $row['is_unique_constraint']) && $type === TableSchema::INDEX_INDEX) {
|
||||
$type = TableSchema::CONSTRAINT_UNIQUE;
|
||||
}
|
||||
|
||||
if ($type === TableSchema::INDEX_INDEX) {
|
||||
$existing = $schema->getIndex($name);
|
||||
} else {
|
||||
$existing = $schema->getConstraint($name);
|
||||
}
|
||||
|
||||
$columns = [$row['column_name']];
|
||||
if ($existing) {
|
||||
$columns = array_merge($existing['columns'], $columns);
|
||||
}
|
||||
|
||||
if ($type === TableSchema::CONSTRAINT_PRIMARY || $type === TableSchema::CONSTRAINT_UNIQUE) {
|
||||
$schema->addConstraint($name, [
|
||||
'type' => $type,
|
||||
'columns' => $columns,
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
$schema->addIndex($name, [
|
||||
'type' => $type,
|
||||
'columns' => $columns,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeIndexes(string $tableName): array
|
||||
{
|
||||
[$schema, $name] = $this->splitTablename($tableName);
|
||||
$sql = $this->describeIndexQuery();
|
||||
$indexes = [];
|
||||
$statement = $this->_driver->execute($sql, [$name, $schema]);
|
||||
foreach ($statement->fetchAll('assoc') as $row) {
|
||||
$type = TableSchema::INDEX_INDEX;
|
||||
$name = $row['index_name'];
|
||||
$constraint = null;
|
||||
if ($row['is_primary_key']) {
|
||||
$constraint = $name;
|
||||
$name = TableSchema::CONSTRAINT_PRIMARY;
|
||||
$type = TableSchema::CONSTRAINT_PRIMARY;
|
||||
}
|
||||
if (($row['is_unique'] || $row['is_unique_constraint']) && $type === TableSchema::INDEX_INDEX) {
|
||||
$type = TableSchema::CONSTRAINT_UNIQUE;
|
||||
}
|
||||
|
||||
if (!isset($indexes[$name])) {
|
||||
$indexes[$name] = [
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'columns' => [],
|
||||
'length' => [],
|
||||
];
|
||||
}
|
||||
if ($row['is_included_column']) {
|
||||
$indexes[$name]['include'][] = $row['column_name'];
|
||||
} else {
|
||||
$indexes[$name]['columns'][] = $row['column_name'];
|
||||
}
|
||||
if ($constraint) {
|
||||
$indexes[$name]['constraint'] = $constraint;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($indexes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query to describe foreign keys
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function describeForeignKeyQuery(): string
|
||||
{
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
|
||||
return 'SELECT FK.[name] AS [foreign_key_name],
|
||||
FK.[delete_referential_action_desc] AS [delete_type],
|
||||
FK.[update_referential_action_desc] AS [update_type],
|
||||
C.name AS [column],
|
||||
RT.name AS [reference_table],
|
||||
RC.name AS [reference_column]
|
||||
FROM sys.foreign_keys FK
|
||||
INNER JOIN sys.foreign_key_columns FKC ON FKC.constraint_object_id = FK.object_id
|
||||
INNER JOIN sys.tables T ON T.object_id = FKC.parent_object_id
|
||||
INNER JOIN sys.tables RT ON RT.object_id = FKC.referenced_object_id
|
||||
INNER JOIN sys.schemas S ON S.schema_id = T.schema_id AND S.schema_id = RT.schema_id
|
||||
INNER JOIN sys.columns C ON C.column_id = FKC.parent_column_id AND C.object_id = FKC.parent_object_id
|
||||
INNER JOIN sys.columns RC ON RC.column_id = FKC.referenced_column_id AND RC.object_id = FKC.referenced_object_id
|
||||
WHERE FK.is_ms_shipped = 0 AND T.name = ? AND S.name = ?
|
||||
ORDER BY FKC.constraint_column_id';
|
||||
// phpcs:enable Generic.Files.LineLength
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeForeignKeys(string $tableName): array
|
||||
{
|
||||
[$schema, $name] = $this->splitTablename($tableName);
|
||||
$sql = $this->describeForeignKeyQuery();
|
||||
$keys = [];
|
||||
$statement = $this->_driver->execute($sql, [$name, $schema]);
|
||||
foreach ($statement->fetchAll('assoc') as $row) {
|
||||
$name = $row['foreign_key_name'];
|
||||
if (!isset($keys[$name])) {
|
||||
$keys[$name] = [
|
||||
'name' => $name,
|
||||
'type' => TableSchema::CONSTRAINT_FOREIGN,
|
||||
'columns' => [],
|
||||
'references' => [$row['reference_table'], []],
|
||||
'update' => $this->_convertOnClause($row['update_type']),
|
||||
'delete' => $this->_convertOnClause($row['delete_type']),
|
||||
];
|
||||
}
|
||||
$keys[$name]['columns'][] = $row['column'];
|
||||
$keys[$name]['references'][1][] = $row['reference_column'];
|
||||
}
|
||||
|
||||
foreach ($keys as $id => $key) {
|
||||
// references.1 is the referenced columns. Backwards compat
|
||||
// requires a single column to be a string, but multiple to be an array.
|
||||
if (count($key['references'][1]) === 1) {
|
||||
$keys[$id]['references'][1] = $key['references'][1][0];
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($keys);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeForeignKeySql(string $tableName, array $config): array
|
||||
{
|
||||
$sql = $this->describeForeignKeyQuery();
|
||||
$schema = $config['schema'] ?? static::DEFAULT_SCHEMA_NAME;
|
||||
|
||||
return [$sql, [$tableName, $schema]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertForeignKeyDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$data = [
|
||||
'type' => TableSchema::CONSTRAINT_FOREIGN,
|
||||
'columns' => [$row['column']],
|
||||
'references' => [$row['reference_table'], $row['reference_column']],
|
||||
'update' => $this->_convertOnClause($row['update_type']),
|
||||
'delete' => $this->_convertOnClause($row['delete_type']),
|
||||
];
|
||||
$name = $row['foreign_key_name'];
|
||||
$schema->addConstraint($name, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeOptions(string $tableName): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function _foreignOnClause(string $on): string
|
||||
{
|
||||
$parent = parent::_foreignOnClause($on);
|
||||
|
||||
return $parent === 'RESTRICT' ? parent::_foreignOnClause(TableSchema::ACTION_NO_ACTION) : $parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function _convertOnClause(string $clause): string
|
||||
{
|
||||
return match ($clause) {
|
||||
'NO_ACTION' => TableSchema::ACTION_NO_ACTION,
|
||||
'CASCADE' => TableSchema::ACTION_CASCADE,
|
||||
'SET_NULL' => TableSchema::ACTION_SET_NULL,
|
||||
'SET_DEFAULT' => TableSchema::ACTION_SET_DEFAULT,
|
||||
default => TableSchema::ACTION_SET_NULL,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function columnSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
$data = $schema->getColumn($name);
|
||||
assert($data !== null);
|
||||
$data['name'] = $name;
|
||||
|
||||
$sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name);
|
||||
if ($sql !== null) {
|
||||
return $sql;
|
||||
}
|
||||
$autoIncrementTypes = [
|
||||
TableSchemaInterface::TYPE_TINYINTEGER,
|
||||
TableSchemaInterface::TYPE_SMALLINTEGER,
|
||||
TableSchemaInterface::TYPE_INTEGER,
|
||||
TableSchemaInterface::TYPE_BIGINTEGER,
|
||||
];
|
||||
$primaryKey = $schema->getPrimaryKey();
|
||||
if (
|
||||
in_array($data['type'], $autoIncrementTypes, true) &&
|
||||
$primaryKey === [$name] &&
|
||||
$name === 'id'
|
||||
) {
|
||||
$data['autoIncrement'] = true;
|
||||
}
|
||||
|
||||
return $this->columnDefinitionSql($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function columnDefinitionSql(array $column): string
|
||||
{
|
||||
$name = $column['name'];
|
||||
$column += [
|
||||
'length' => null,
|
||||
'precision' => null,
|
||||
];
|
||||
$out = $this->_driver->quoteIdentifier($name);
|
||||
$typeMap = [
|
||||
TableSchemaInterface::TYPE_TINYINTEGER => ' TINYINT',
|
||||
TableSchemaInterface::TYPE_SMALLINTEGER => ' SMALLINT',
|
||||
TableSchemaInterface::TYPE_INTEGER => ' INTEGER',
|
||||
TableSchemaInterface::TYPE_BIGINTEGER => ' BIGINT',
|
||||
TableSchemaInterface::TYPE_BINARY_UUID => ' UNIQUEIDENTIFIER',
|
||||
TableSchemaInterface::TYPE_BOOLEAN => ' BIT',
|
||||
TableSchemaInterface::TYPE_CHAR => ' NCHAR',
|
||||
TableSchemaInterface::TYPE_STRING => ' NVARCHAR',
|
||||
TableSchemaInterface::TYPE_FLOAT => ' FLOAT',
|
||||
TableSchemaInterface::TYPE_DECIMAL => ' DECIMAL',
|
||||
TableSchemaInterface::TYPE_DATE => ' DATE',
|
||||
TableSchemaInterface::TYPE_TIME => ' TIME',
|
||||
TableSchemaInterface::TYPE_DATETIME => ' DATETIME2',
|
||||
TableSchemaInterface::TYPE_DATETIME_FRACTIONAL => ' DATETIME2',
|
||||
TableSchemaInterface::TYPE_TIMESTAMP => ' DATETIME2',
|
||||
TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL => ' DATETIME2',
|
||||
TableSchemaInterface::TYPE_TIMESTAMP_TIMEZONE => ' DATETIME2',
|
||||
TableSchemaInterface::TYPE_UUID => ' UNIQUEIDENTIFIER',
|
||||
TableSchemaInterface::TYPE_NATIVE_UUID => ' UNIQUEIDENTIFIER',
|
||||
TableSchemaInterface::TYPE_JSON => ' NVARCHAR(MAX)',
|
||||
TableSchemaInterface::TYPE_GEOMETRY => ' GEOMETRY',
|
||||
TableSchemaInterface::TYPE_POINT => ' GEOGRAPHY',
|
||||
TableSchemaInterface::TYPE_LINESTRING => ' GEOGRAPHY',
|
||||
TableSchemaInterface::TYPE_POLYGON => ' GEOGRAPHY',
|
||||
];
|
||||
|
||||
$foundType = false;
|
||||
if (isset($typeMap[$column['type']])) {
|
||||
$out .= $typeMap[$column['type']];
|
||||
$foundType = true;
|
||||
}
|
||||
|
||||
$hasLength = [
|
||||
TableSchemaInterface::TYPE_CHAR,
|
||||
TableSchemaInterface::TYPE_STRING,
|
||||
TableSchemaInterface::TYPE_BINARY,
|
||||
];
|
||||
$autoIncrementTypes = [
|
||||
TableSchemaInterface::TYPE_TINYINTEGER,
|
||||
TableSchemaInterface::TYPE_SMALLINTEGER,
|
||||
TableSchemaInterface::TYPE_INTEGER,
|
||||
TableSchemaInterface::TYPE_BIGINTEGER,
|
||||
];
|
||||
$autoIncrement = (bool)($column['autoIncrement'] ?? false);
|
||||
if (in_array($column['type'], $autoIncrementTypes, true) && $autoIncrement) {
|
||||
$out .= ' IDENTITY(1, 1)';
|
||||
$foundType = true;
|
||||
unset($column['default']);
|
||||
}
|
||||
|
||||
if ($column['type'] === TableSchemaInterface::TYPE_STRING && !isset($column['length'])) {
|
||||
$column['length'] = TableSchema::LENGTH_TINY;
|
||||
} elseif (
|
||||
$column['type'] === TableSchemaInterface::TYPE_TEXT &&
|
||||
$column['length'] !== TableSchema::LENGTH_TINY
|
||||
) {
|
||||
$out .= ' NVARCHAR(MAX)';
|
||||
$foundType = true;
|
||||
}
|
||||
|
||||
if ($column['type'] === TableSchemaInterface::TYPE_BINARY) {
|
||||
if (
|
||||
!isset($column['length'])
|
||||
|| in_array($column['length'], [TableSchema::LENGTH_MEDIUM, TableSchema::LENGTH_LONG], true)
|
||||
) {
|
||||
$column['length'] = 'MAX';
|
||||
}
|
||||
|
||||
if ($column['length'] === 1) {
|
||||
$out .= ' BINARY';
|
||||
} else {
|
||||
$out .= ' VARBINARY';
|
||||
}
|
||||
$foundType = true;
|
||||
}
|
||||
|
||||
if ($column['type'] === TableSchemaInterface::TYPE_TEXT && $column['length'] === TableSchema::LENGTH_TINY) {
|
||||
$out .= ' NVARCHAR';
|
||||
$hasLength[] = $column['type'];
|
||||
$foundType = true;
|
||||
}
|
||||
if (!$foundType) {
|
||||
$out .= ' ' . strtoupper($column['type']);
|
||||
$hasLength[] = $column['type'];
|
||||
}
|
||||
if (in_array($column['type'], $hasLength, true) && isset($column['length'])) {
|
||||
$out .= '(' . $column['length'] . ')';
|
||||
}
|
||||
|
||||
$hasCollate = [
|
||||
TableSchemaInterface::TYPE_TEXT,
|
||||
TableSchemaInterface::TYPE_STRING,
|
||||
TableSchemaInterface::TYPE_CHAR,
|
||||
];
|
||||
if (in_array($column['type'], $hasCollate, true) && isset($column['collate']) && $column['collate'] !== '') {
|
||||
$out .= ' COLLATE ' . $column['collate'];
|
||||
}
|
||||
|
||||
$precisionTypes = [
|
||||
TableSchemaInterface::TYPE_FLOAT,
|
||||
TableSchemaInterface::TYPE_DATETIME,
|
||||
TableSchemaInterface::TYPE_DATETIME_FRACTIONAL,
|
||||
TableSchemaInterface::TYPE_TIMESTAMP,
|
||||
TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL,
|
||||
];
|
||||
if (in_array($column['type'], $precisionTypes, true) && isset($column['precision'])) {
|
||||
$out .= '(' . (int)$column['precision'] . ')';
|
||||
}
|
||||
|
||||
if (
|
||||
$column['type'] === TableSchemaInterface::TYPE_DECIMAL &&
|
||||
(isset($column['length']) || isset($column['precision']))
|
||||
) {
|
||||
$out .= '(' . (int)$column['length'] . ',' . (int)$column['precision'] . ')';
|
||||
}
|
||||
|
||||
if (isset($column['null']) && $column['null'] === false) {
|
||||
$out .= ' NOT NULL';
|
||||
}
|
||||
|
||||
$dateTimeTypes = [
|
||||
TableSchemaInterface::TYPE_DATETIME,
|
||||
TableSchemaInterface::TYPE_DATETIME_FRACTIONAL,
|
||||
TableSchemaInterface::TYPE_TIMESTAMP,
|
||||
TableSchemaInterface::TYPE_TIMESTAMP_FRACTIONAL,
|
||||
];
|
||||
$dateTimeDefaults = [
|
||||
'current_timestamp',
|
||||
'getdate()',
|
||||
'getutcdate()',
|
||||
'sysdatetime()',
|
||||
'sysutcdatetime()',
|
||||
'sysdatetimeoffset()',
|
||||
];
|
||||
if (
|
||||
isset($column['default']) &&
|
||||
in_array($column['type'], $dateTimeTypes, true) &&
|
||||
is_string($column['default']) &&
|
||||
in_array(strtolower($column['default']), $dateTimeDefaults, true)
|
||||
) {
|
||||
$out .= ' DEFAULT ' . strtoupper($column['default']);
|
||||
} elseif (isset($column['default'])) {
|
||||
$default = is_bool($column['default'])
|
||||
? (int)$column['default']
|
||||
: $this->_driver->schemaValue($column['default']);
|
||||
$out .= ' DEFAULT ' . $default;
|
||||
} elseif (isset($column['null']) && $column['null'] !== false) {
|
||||
$out .= ' DEFAULT NULL';
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addConstraintSql(TableSchema $schema): array
|
||||
{
|
||||
$sqlPattern = 'ALTER TABLE %s ADD %s;';
|
||||
$sql = [];
|
||||
|
||||
foreach ($schema->constraints() as $name) {
|
||||
$constraint = $schema->getConstraint($name);
|
||||
assert($constraint !== null);
|
||||
if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) {
|
||||
$tableName = $this->_driver->quoteIdentifier($schema->name());
|
||||
$sql[] = sprintf($sqlPattern, $tableName, $this->constraintSql($schema, $name));
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function dropConstraintSql(TableSchema $schema): array
|
||||
{
|
||||
$sqlPattern = 'ALTER TABLE %s DROP CONSTRAINT %s;';
|
||||
$sql = [];
|
||||
|
||||
foreach ($schema->constraints() as $name) {
|
||||
$constraint = $schema->getConstraint($name);
|
||||
assert($constraint !== null);
|
||||
if ($constraint['type'] === TableSchema::CONSTRAINT_FOREIGN) {
|
||||
$tableName = $this->_driver->quoteIdentifier($schema->name());
|
||||
$constraintName = $this->_driver->quoteIdentifier($name);
|
||||
$sql[] = sprintf($sqlPattern, $tableName, $constraintName);
|
||||
}
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function indexSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
$index = $schema->index($name);
|
||||
$columns = array_map(
|
||||
$this->_driver->quoteIdentifier(...),
|
||||
(array)$index->getColumns(),
|
||||
);
|
||||
$include = '';
|
||||
$included = $index->getInclude();
|
||||
if ($included !== null) {
|
||||
$included = array_map(
|
||||
$this->_driver->quoteIdentifier(...),
|
||||
$included,
|
||||
);
|
||||
$include = sprintf(' INCLUDE (%s)', implode(', ', $included));
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'CREATE INDEX %s ON %s (%s)%s',
|
||||
$this->_driver->quoteIdentifier($name),
|
||||
$this->_driver->quoteIdentifier($schema->name()),
|
||||
implode(', ', $columns),
|
||||
$include,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function constraintSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
$data = $schema->getConstraint($name);
|
||||
assert($data !== null);
|
||||
$out = 'CONSTRAINT ' . $this->_driver->quoteIdentifier($name);
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_PRIMARY) {
|
||||
$out = 'PRIMARY KEY';
|
||||
}
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_UNIQUE) {
|
||||
$out .= ' UNIQUE';
|
||||
}
|
||||
|
||||
return $this->_keySql($out, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for generating key SQL snippets.
|
||||
*
|
||||
* @param string $prefix The key prefix
|
||||
* @param array $data Key data.
|
||||
* @return string
|
||||
*/
|
||||
protected function _keySql(string $prefix, array $data): string
|
||||
{
|
||||
$columns = array_map(
|
||||
$this->_driver->quoteIdentifier(...),
|
||||
$data['columns'],
|
||||
);
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_FOREIGN) {
|
||||
return $prefix . sprintf(
|
||||
' FOREIGN KEY (%s) REFERENCES %s (%s) ON UPDATE %s ON DELETE %s',
|
||||
implode(', ', $columns),
|
||||
$this->_driver->quoteIdentifier($data['references'][0]),
|
||||
$this->_convertConstraintColumns($data['references'][1]),
|
||||
$this->_foreignOnClause($data['update']),
|
||||
$this->_foreignOnClause($data['delete']),
|
||||
);
|
||||
}
|
||||
|
||||
return $prefix . ' (' . implode(', ', $columns) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function createTableSql(TableSchema $schema, array $columns, array $constraints, array $indexes): array
|
||||
{
|
||||
$content = array_merge($columns, $constraints);
|
||||
$content = implode(",\n", array_filter($content));
|
||||
$tableName = $this->_driver->quoteIdentifier($schema->name());
|
||||
$out = [];
|
||||
$out[] = sprintf("CREATE TABLE %s (\n%s\n)", $tableName, $content);
|
||||
foreach ($indexes as $index) {
|
||||
$out[] = $index;
|
||||
}
|
||||
foreach ($schema->columns() as $name) {
|
||||
$column = $schema->getColumn($name);
|
||||
$comment = $column['comment'] ?? null;
|
||||
if ($comment !== null) {
|
||||
$out[] = $this->columnCommentSql($schema, $name, $comment);
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SQL to create a column comment.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table schema.
|
||||
* @param string $name The column name.
|
||||
* @param string $comment The column comment.
|
||||
* @return string
|
||||
*/
|
||||
protected function columnCommentSql(TableSchema $schema, string $name, string $comment): string
|
||||
{
|
||||
$tableName = $this->_driver->quoteIdentifier($schema->name());
|
||||
$columnName = $this->_driver->quoteIdentifier($name);
|
||||
$comment = $this->_driver->schemaValue($comment);
|
||||
|
||||
return sprintf(
|
||||
"EXEC sp_addextendedproperty N'MS_Description', %s, N'SCHEMA', N'dbo', N'TABLE', %s, N'COLUMN', %s;",
|
||||
$comment,
|
||||
$tableName,
|
||||
$columnName,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function truncateTableSql(TableSchema $schema): array
|
||||
{
|
||||
$name = $this->_driver->quoteIdentifier($schema->name());
|
||||
$queries = [
|
||||
sprintf('DELETE FROM %s', $name),
|
||||
];
|
||||
|
||||
// Restart identity sequences
|
||||
$pk = $schema->getPrimaryKey();
|
||||
if (count($pk) === 1) {
|
||||
$column = $schema->getColumn($pk[0]);
|
||||
assert($column !== null);
|
||||
if (in_array($column['type'], ['integer', 'biginteger'])) {
|
||||
$queries[] = sprintf(
|
||||
"IF EXISTS (SELECT * FROM sys.identity_columns WHERE OBJECT_NAME(OBJECT_ID) = '%s' AND " .
|
||||
"last_value IS NOT NULL) DBCC CHECKIDENT('%s', RESEED, 0)",
|
||||
$schema->name(),
|
||||
$schema->name(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $queries;
|
||||
}
|
||||
}
|
||||
+1054
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,381 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.5.0
|
||||
* @license https://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Schema;
|
||||
|
||||
use Cake\Datasource\SchemaInterface;
|
||||
|
||||
/**
|
||||
* An interface used by database TableSchema objects.
|
||||
*
|
||||
* @method \Cake\Database\Schema\Column column(string $name)
|
||||
* @method \Cake\Database\Schema\Index index(string $name)
|
||||
* @method \Cake\Database\Schema\Constraint constraint(string $name)
|
||||
*/
|
||||
interface TableSchemaInterface extends SchemaInterface
|
||||
{
|
||||
/**
|
||||
* Binary column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_BINARY = 'binary';
|
||||
|
||||
/**
|
||||
* Binary UUID column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_BINARY_UUID = 'binaryuuid';
|
||||
|
||||
/**
|
||||
* Date column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_DATE = 'date';
|
||||
|
||||
/**
|
||||
* Datetime column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_DATETIME = 'datetime';
|
||||
|
||||
/**
|
||||
* Datetime with fractional seconds column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_DATETIME_FRACTIONAL = 'datetimefractional';
|
||||
|
||||
/**
|
||||
* Time column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_TIME = 'time';
|
||||
|
||||
/**
|
||||
* Year column type
|
||||
*
|
||||
* Currently only implemented in MySQL
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_YEAR = 'year';
|
||||
|
||||
/**
|
||||
* Timestamp column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_TIMESTAMP = 'timestamp';
|
||||
|
||||
/**
|
||||
* Timestamp with fractional seconds column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_TIMESTAMP_FRACTIONAL = 'timestampfractional';
|
||||
|
||||
/**
|
||||
* Timestamp with time zone column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_TIMESTAMP_TIMEZONE = 'timestamptimezone';
|
||||
|
||||
/**
|
||||
* Datetime interval. Only implemented in postgres.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_INTERVAL = 'interval';
|
||||
|
||||
/**
|
||||
* JSON column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_JSON = 'json';
|
||||
|
||||
/**
|
||||
* String column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_STRING = 'string';
|
||||
|
||||
/**
|
||||
* Char column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_CHAR = 'char';
|
||||
|
||||
/**
|
||||
* Case-insensitive text column type.
|
||||
*
|
||||
* Only implemented in postgres
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_CITEXT = 'citext';
|
||||
|
||||
/**
|
||||
* Text column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_TEXT = 'text';
|
||||
|
||||
/**
|
||||
* Tiny Integer column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_TINYINTEGER = 'tinyinteger';
|
||||
|
||||
/**
|
||||
* Small Integer column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_SMALLINTEGER = 'smallinteger';
|
||||
|
||||
/**
|
||||
* Integer column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_INTEGER = 'integer';
|
||||
|
||||
/**
|
||||
* Big Integer column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_BIGINTEGER = 'biginteger';
|
||||
|
||||
/**
|
||||
* Float column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_FLOAT = 'float';
|
||||
|
||||
/**
|
||||
* Decimal column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_DECIMAL = 'decimal';
|
||||
|
||||
/**
|
||||
* Boolean column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_BOOLEAN = 'boolean';
|
||||
|
||||
/**
|
||||
* UUID column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_UUID = 'uuid';
|
||||
|
||||
/**
|
||||
* Native UUID column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_NATIVE_UUID = 'nativeuuid';
|
||||
|
||||
/**
|
||||
* Geometry column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_GEOMETRY = 'geometry';
|
||||
|
||||
/**
|
||||
* Point column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_POINT = 'point';
|
||||
|
||||
/**
|
||||
* Linestring column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_LINESTRING = 'linestring';
|
||||
|
||||
/**
|
||||
* Polgon column type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_POLYGON = 'polygon';
|
||||
|
||||
/**
|
||||
* INET type. Only implemented in postgres.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_INET = 'inet';
|
||||
|
||||
/**
|
||||
* CIDR type. Only implemented in postgres.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_CIDR = 'cidr';
|
||||
|
||||
/**
|
||||
* Macaddr type. Only implemented in postgres.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TYPE_MACADDR = 'macaddr';
|
||||
|
||||
/**
|
||||
* Geospatial column types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public const GEOSPATIAL_TYPES = [
|
||||
self::TYPE_GEOMETRY,
|
||||
self::TYPE_POINT,
|
||||
self::TYPE_LINESTRING,
|
||||
self::TYPE_POLYGON,
|
||||
];
|
||||
|
||||
/**
|
||||
* Check whether a table has an autoIncrement column defined.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAutoincrement(): bool;
|
||||
|
||||
/**
|
||||
* Sets whether the table is temporary in the database.
|
||||
*
|
||||
* @param bool $temporary Whether the table is to be temporary.
|
||||
* @return $this
|
||||
*/
|
||||
public function setTemporary(bool $temporary);
|
||||
|
||||
/**
|
||||
* Gets whether the table is temporary in the database.
|
||||
*
|
||||
* @return bool The current temporary setting.
|
||||
*/
|
||||
public function isTemporary(): bool;
|
||||
|
||||
/**
|
||||
* Get the column(s) used for the primary key.
|
||||
*
|
||||
* @return array<string> Column name(s) for the primary key. An
|
||||
* empty list will be returned when the table has no primary key.
|
||||
*/
|
||||
public function getPrimaryKey(): array;
|
||||
|
||||
/**
|
||||
* Add an index.
|
||||
*
|
||||
* Used to add indexes, and full text indexes in platforms that support
|
||||
* them.
|
||||
*
|
||||
* ### Attributes
|
||||
*
|
||||
* - `type` The type of index being added.
|
||||
* - `columns` The columns in the index.
|
||||
*
|
||||
* @param string $name The name of the index.
|
||||
* @param array<string, mixed>|string $attrs The attributes for the index.
|
||||
* If string it will be used as `type`.
|
||||
* @return $this
|
||||
* @throws \Cake\Database\Exception\DatabaseException
|
||||
*/
|
||||
public function addIndex(string $name, array|string $attrs);
|
||||
|
||||
/**
|
||||
* Read information about an index based on name.
|
||||
*
|
||||
* @param string $name The name of the index.
|
||||
* @return array<string, mixed>|null Array of index data, or null
|
||||
*/
|
||||
public function getIndex(string $name): ?array;
|
||||
|
||||
/**
|
||||
* Get the names of all the indexes in the table.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function indexes(): array;
|
||||
|
||||
/**
|
||||
* Add a constraint.
|
||||
*
|
||||
* Used to add constraints to a table. For example primary keys, unique
|
||||
* keys, check constraints and foreign keys.
|
||||
*
|
||||
* ### Attributes
|
||||
*
|
||||
* - `type` The type of constraint being added.
|
||||
* - `columns` The columns in the index.
|
||||
* - `references` The table, column a foreign key references.
|
||||
* - `update` The behavior on update. Options are 'restrict', 'setNull', 'cascade', 'noAction'.
|
||||
* - `delete` The behavior on delete. Options are 'restrict', 'setNull', 'cascade', 'noAction'.
|
||||
* - `expression` The SQL expression for check constraints.
|
||||
*
|
||||
* The default for 'update' & 'delete' is 'cascade'.
|
||||
*
|
||||
* @param string $name The name of the constraint.
|
||||
* @param array<string, mixed>|string $attrs The attributes for the constraint.
|
||||
* If string it will be used as `type`.
|
||||
* @return $this
|
||||
* @throws \Cake\Database\Exception\DatabaseException
|
||||
*/
|
||||
public function addConstraint(string $name, array|string $attrs);
|
||||
|
||||
/**
|
||||
* Read information about a constraint based on name.
|
||||
*
|
||||
* @param string $name The name of the constraint.
|
||||
* @return array<string, mixed>|null Array of constraint data, or null
|
||||
*/
|
||||
public function getConstraint(string $name): ?array;
|
||||
|
||||
/**
|
||||
* Remove a constraint.
|
||||
*
|
||||
* @param string $name Name of the constraint to remove
|
||||
* @return $this
|
||||
*/
|
||||
public function dropConstraint(string $name);
|
||||
|
||||
/**
|
||||
* Get the names of all the constraints in the table.
|
||||
*
|
||||
* @return array<string>
|
||||
*/
|
||||
public function constraints(): array;
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc.
|
||||
* (https://github.com/cakephp/migrations/tree/master/LICENSE.txt)
|
||||
* @link https://cakephp.org CakePHP(tm) Project
|
||||
* @since 5.3.0
|
||||
* @license https://opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Cake\Database\Schema;
|
||||
|
||||
/**
|
||||
* UniqueKey class
|
||||
*
|
||||
* Models a unique key constraint, and provides methods to set driver specific attributes.
|
||||
*/
|
||||
class UniqueKey extends Constraint
|
||||
{
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $name The name of the constraint.
|
||||
* @param array<string> $columns The columns to constraint.
|
||||
* @param array<string, int>|null $length The length of the columns, if applicable.
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $name,
|
||||
protected array $columns,
|
||||
protected ?array $length = null,
|
||||
) {
|
||||
$this->type = self::UNIQUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the constraint columns.
|
||||
*
|
||||
* @param array<string>|string $columns Columns
|
||||
* @return $this
|
||||
*/
|
||||
public function setColumns(string|array $columns)
|
||||
{
|
||||
$this->columns = (array)$columns;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constraint columns.
|
||||
*
|
||||
* @return ?array<string>
|
||||
*/
|
||||
public function getColumns(): ?array
|
||||
{
|
||||
return $this->columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the constraint type.
|
||||
*
|
||||
* @param string $type Type
|
||||
* @return $this
|
||||
*/
|
||||
public function setType(string $type)
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constraint type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the constraint name.
|
||||
*
|
||||
* @param string $name Name
|
||||
* @return $this
|
||||
*/
|
||||
public function setName(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constraint name.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the constraint length.
|
||||
*
|
||||
* In MySQL unique constraints can have limit clauses to control the number of
|
||||
* characters indexed in text and char columns.
|
||||
*
|
||||
* @param array<string, int> $length array of length values
|
||||
* @return $this
|
||||
*/
|
||||
public function setLength(array $length)
|
||||
{
|
||||
$this->length = $length;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constraint length.
|
||||
*
|
||||
* Can be an array of column names and lengths under MySQL.
|
||||
*
|
||||
* @return array<string, int>|null
|
||||
*/
|
||||
public function getLength(): ?array
|
||||
{
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a constraint to an array that is compatible
|
||||
* with the constructor.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return [
|
||||
'name' => $this->name,
|
||||
'type' => $this->type,
|
||||
'columns' => $this->columns,
|
||||
'length' => $this->length,
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user