INIT
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
deprecationWarning(
|
||||
'Since 4.1.0: Cake\Database\Schema\BaseSchema is deprecated. ' .
|
||||
'Use Cake\Database\Schema\SchemaDialect instead.'
|
||||
);
|
||||
class_exists('Cake\Database\Schema\SchemaDialect');
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
<?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 $cacher;
|
||||
|
||||
/**
|
||||
* The decorated schema collection
|
||||
*
|
||||
* @var \Cake\Database\Schema\CollectionInterface
|
||||
*/
|
||||
protected $collection;
|
||||
|
||||
/**
|
||||
* The cache key prefix
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
<?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;
|
||||
use Cake\Database\Exception\DatabaseException;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* Represents a database schema collection
|
||||
*
|
||||
* Used to access information about the tables,
|
||||
* and other data in a database.
|
||||
*/
|
||||
class Collection implements CollectionInterface
|
||||
{
|
||||
/**
|
||||
* Connection object
|
||||
*
|
||||
* @var \Cake\Database\Connection
|
||||
*/
|
||||
protected $_connection;
|
||||
|
||||
/**
|
||||
* Schema dialect instance.
|
||||
*
|
||||
* @var \Cake\Database\Schema\SchemaDialect
|
||||
*/
|
||||
protected $_dialect;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \Cake\Database\Connection $connection The connection instance.
|
||||
*/
|
||||
public function __construct(Connection $connection)
|
||||
{
|
||||
$this->_connection = $connection;
|
||||
$this->_dialect = $connection->getDriver()->schemaDialect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->_dialect->listTablesWithoutViewsSql($this->_connection->getDriver()->config());
|
||||
$result = [];
|
||||
$statement = $this->_connection->execute($sql, $params);
|
||||
while ($row = $statement->fetch()) {
|
||||
$result[] = $row[0];
|
||||
}
|
||||
$statement->closeCursor();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
[$sql, $params] = $this->_dialect->listTablesSql($this->_connection->getDriver()->config());
|
||||
$result = [];
|
||||
$statement = $this->_connection->execute($sql, $params);
|
||||
while ($row = $statement->fetch()) {
|
||||
$result[] = $row[0];
|
||||
}
|
||||
$statement->closeCursor();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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\TableSchema Object with column metadata.
|
||||
* @throws \Cake\Database\Exception\DatabaseException when table cannot be described.
|
||||
*/
|
||||
public function describe(string $name, array $options = []): TableSchemaInterface
|
||||
{
|
||||
$config = $this->_connection->getDriver()->config();
|
||||
if (strpos($name, '.')) {
|
||||
[$config['schema'], $name] = explode('.', $name);
|
||||
}
|
||||
$table = $this->_connection->getDriver()->newTableSchema($name);
|
||||
|
||||
$this->_reflect('Column', $name, $config, $table);
|
||||
if (count($table->columns()) === 0) {
|
||||
throw new DatabaseException(sprintf('Cannot describe %s. It has 0 columns.', $name));
|
||||
}
|
||||
|
||||
$this->_reflect('Index', $name, $config, $table);
|
||||
$this->_reflect('ForeignKey', $name, $config, $table);
|
||||
$this->_reflect('Options', $name, $config, $table);
|
||||
|
||||
return $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for running each step of the reflection process.
|
||||
*
|
||||
* @param string $stage The stage name.
|
||||
* @param string $name The table name.
|
||||
* @param array<string, mixed> $config The config data.
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table schema instance.
|
||||
* @return void
|
||||
* @throws \Cake\Database\Exception\DatabaseException on query failure.
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::describeColumnSql
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::describeIndexSql
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::describeForeignKeySql
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::describeOptionsSql
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::convertColumnDescription
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::convertIndexDescription
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::convertForeignKeyDescription
|
||||
* @uses \Cake\Database\Schema\SchemaDialect::convertOptionsDescription
|
||||
*/
|
||||
protected function _reflect(string $stage, string $name, array $config, TableSchema $schema): void
|
||||
{
|
||||
$describeMethod = "describe{$stage}Sql";
|
||||
$convertMethod = "convert{$stage}Description";
|
||||
|
||||
[$sql, $params] = $this->_dialect->{$describeMethod}($name, $config);
|
||||
if (empty($sql)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$statement = $this->_connection->execute($sql, $params);
|
||||
} catch (PDOException $e) {
|
||||
throw new DatabaseException($e->getMessage(), 500, $e);
|
||||
}
|
||||
/** @psalm-suppress PossiblyFalseIterator */
|
||||
foreach ($statement->fetchAll('assoc') as $row) {
|
||||
$this->_dialect->{$convertMethod}($schema, $row);
|
||||
}
|
||||
$statement->closeCursor();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
deprecationWarning(
|
||||
'Since 4.1.0: Cake\Database\Schema\MysqlSchema is deprecated. ' .
|
||||
'Use Cake\Database\Schema\MysqlSchemaDialect instead.'
|
||||
);
|
||||
class_exists('Cake\Database\Schema\MysqlSchemaDialect');
|
||||
@@ -0,0 +1,669 @@
|
||||
<?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\DriverInterface;
|
||||
use Cake\Database\Exception\DatabaseException;
|
||||
|
||||
/**
|
||||
* Schema generation/reflection features for MySQL
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class MysqlSchemaDialect extends SchemaDialect
|
||||
{
|
||||
/**
|
||||
* The driver instance being used.
|
||||
*
|
||||
* @var \Cake\Database\Driver\Mysql
|
||||
*/
|
||||
protected $_driver;
|
||||
|
||||
/**
|
||||
* 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<mixed> An array of (sql, params) to execute.
|
||||
*/
|
||||
public function listTablesSql(array $config): array
|
||||
{
|
||||
return ['SHOW FULL TABLES FROM ' . $this->_driver->quoteIdentifier($config['database']), []];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return [
|
||||
'SHOW FULL TABLES FROM ' . $this->_driver->quoteIdentifier($config['database'])
|
||||
. ' WHERE TABLE_TYPE = "BASE TABLE"'
|
||||
, []];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeColumnSql(string $tableName, array $config): array
|
||||
{
|
||||
return ['SHOW FULL COLUMNS FROM ' . $this->_driver->quoteIdentifier($tableName), []];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeIndexSql(string $tableName, array $config): array
|
||||
{
|
||||
return ['SHOW INDEXES FROM ' . $this->_driver->quoteIdentifier($tableName), []];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeOptionsSql(string $tableName, array $config): array
|
||||
{
|
||||
return ['SHOW TABLE STATUS WHERE Name = ?', [$tableName]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertOptionsDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$schema->setOptions([
|
||||
'engine' => $row['Engine'],
|
||||
'collation' => $row['Collation'],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a MySQL column type into an abstract type.
|
||||
*
|
||||
* The returned type will be a type that Cake\Database\TypeFactory can handle.
|
||||
*
|
||||
* @param string $column The column type + length
|
||||
* @return array<string, mixed> Array of column information.
|
||||
* @throws \Cake\Database\Exception\DatabaseException When column type cannot be parsed.
|
||||
*/
|
||||
protected function _convertColumn(string $column): array
|
||||
{
|
||||
preg_match('/([a-z]+)(?:\(([0-9,]+)\))?\s*([a-z]+)?/i', $column, $matches);
|
||||
if (empty($matches)) {
|
||||
throw new DatabaseException(sprintf('Unable to parse column type from "%s"', $column));
|
||||
}
|
||||
|
||||
$col = strtolower($matches[1]);
|
||||
$length = $precision = $scale = null;
|
||||
if (isset($matches[2]) && strlen($matches[2])) {
|
||||
$length = $matches[2];
|
||||
if (strpos($matches[2], ',') !== false) {
|
||||
[$length, $precision] = explode(',', $length);
|
||||
}
|
||||
$length = (int)$length;
|
||||
$precision = (int)$precision;
|
||||
}
|
||||
|
||||
$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 (in_array($col, ['datetime', 'timestamp'])) {
|
||||
$typeName = $col;
|
||||
if ($length > 0) {
|
||||
$typeName = $col . 'fractional';
|
||||
}
|
||||
|
||||
return ['type' => $typeName, 'length' => null, 'precision' => $length];
|
||||
}
|
||||
|
||||
if (($col === 'tinyint' && $length === 1) || $col === 'boolean') {
|
||||
return ['type' => TableSchema::TYPE_BOOLEAN, 'length' => null];
|
||||
}
|
||||
|
||||
$unsigned = (isset($matches[3]) && strtolower($matches[3]) === 'unsigned');
|
||||
if (strpos($col, 'bigint') !== false || $col === 'bigint') {
|
||||
return ['type' => TableSchema::TYPE_BIGINTEGER, 'length' => null, 'unsigned' => $unsigned];
|
||||
}
|
||||
if ($col === 'tinyint') {
|
||||
return ['type' => TableSchema::TYPE_TINYINTEGER, 'length' => null, 'unsigned' => $unsigned];
|
||||
}
|
||||
if ($col === 'smallint') {
|
||||
return ['type' => TableSchema::TYPE_SMALLINTEGER, 'length' => null, 'unsigned' => $unsigned];
|
||||
}
|
||||
if (in_array($col, ['int', 'integer', 'mediumint'])) {
|
||||
return ['type' => TableSchema::TYPE_INTEGER, 'length' => null, 'unsigned' => $unsigned];
|
||||
}
|
||||
if ($col === 'char' && $length === 36) {
|
||||
return ['type' => TableSchema::TYPE_UUID, 'length' => null];
|
||||
}
|
||||
if ($col === 'char') {
|
||||
return ['type' => TableSchema::TYPE_CHAR, 'length' => $length];
|
||||
}
|
||||
if (strpos($col, 'char') !== false) {
|
||||
return ['type' => TableSchema::TYPE_STRING, 'length' => $length];
|
||||
}
|
||||
if (strpos($col, 'text') !== false) {
|
||||
$lengthName = substr($col, 0, -4);
|
||||
$length = TableSchema::$columnLengths[$lengthName] ?? null;
|
||||
|
||||
return ['type' => TableSchema::TYPE_TEXT, 'length' => $length];
|
||||
}
|
||||
if ($col === 'binary' && $length === 16) {
|
||||
return ['type' => TableSchema::TYPE_BINARY_UUID, 'length' => null];
|
||||
}
|
||||
if (strpos($col, 'blob') !== false || in_array($col, ['binary', 'varbinary'])) {
|
||||
$lengthName = substr($col, 0, -4);
|
||||
$length = TableSchema::$columnLengths[$lengthName] ?? $length;
|
||||
|
||||
return ['type' => TableSchema::TYPE_BINARY, 'length' => $length];
|
||||
}
|
||||
if (strpos($col, 'float') !== false || strpos($col, 'double') !== false) {
|
||||
return [
|
||||
'type' => TableSchema::TYPE_FLOAT,
|
||||
'length' => $length,
|
||||
'precision' => $precision,
|
||||
'unsigned' => $unsigned,
|
||||
];
|
||||
}
|
||||
if (strpos($col, 'decimal') !== false) {
|
||||
return [
|
||||
'type' => TableSchema::TYPE_DECIMAL,
|
||||
'length' => $length,
|
||||
'precision' => $precision,
|
||||
'unsigned' => $unsigned,
|
||||
];
|
||||
}
|
||||
|
||||
if (strpos($col, 'json') !== false) {
|
||||
return ['type' => TableSchema::TYPE_JSON, 'length' => null];
|
||||
}
|
||||
|
||||
return ['type' => TableSchema::TYPE_STRING, 'length' => null];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertColumnDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$field = $this->_convertColumn($row['Type']);
|
||||
$field += [
|
||||
'null' => $row['Null'] === 'YES',
|
||||
'default' => $row['Default'],
|
||||
'collate' => $row['Collation'],
|
||||
'comment' => $row['Comment'],
|
||||
];
|
||||
if (isset($row['Extra']) && $row['Extra'] === 'auto_increment') {
|
||||
$field['autoIncrement'] = true;
|
||||
}
|
||||
$schema->addColumn($row['Field'], $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertIndexDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$type = null;
|
||||
$columns = $length = [];
|
||||
|
||||
$name = $row['Key_name'];
|
||||
if ($name === 'PRIMARY') {
|
||||
$name = $type = TableSchema::CONSTRAINT_PRIMARY;
|
||||
}
|
||||
|
||||
if (!empty($row['Column_name'])) {
|
||||
$columns[] = $row['Column_name'];
|
||||
}
|
||||
|
||||
if ($row['Index_type'] === 'FULLTEXT') {
|
||||
$type = TableSchema::INDEX_FULLTEXT;
|
||||
} elseif ((int)$row['Non_unique'] === 0 && $type !== 'primary') {
|
||||
$type = TableSchema::CONSTRAINT_UNIQUE;
|
||||
} elseif ($type !== 'primary') {
|
||||
$type = TableSchema::INDEX_INDEX;
|
||||
}
|
||||
|
||||
if (!empty($row['Sub_part'])) {
|
||||
$length[$row['Column_name']] = $row['Sub_part'];
|
||||
}
|
||||
$isIndex = (
|
||||
$type === TableSchema::INDEX_INDEX ||
|
||||
$type === TableSchema::INDEX_FULLTEXT
|
||||
);
|
||||
if ($isIndex) {
|
||||
$existing = $schema->getIndex($name);
|
||||
} else {
|
||||
$existing = $schema->getConstraint($name);
|
||||
}
|
||||
|
||||
// MySQL multi column indexes come back as multiple rows.
|
||||
if (!empty($existing)) {
|
||||
$columns = array_merge($existing['columns'], $columns);
|
||||
$length = array_merge($existing['length'], $length);
|
||||
}
|
||||
if ($isIndex) {
|
||||
$schema->addIndex($name, [
|
||||
'type' => $type,
|
||||
'columns' => $columns,
|
||||
'length' => $length,
|
||||
]);
|
||||
} else {
|
||||
$schema->addConstraint($name, [
|
||||
'type' => $type,
|
||||
'columns' => $columns,
|
||||
'length' => $length,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeForeignKeySql(string $tableName, array $config): array
|
||||
{
|
||||
$sql = 'SELECT * FROM information_schema.key_column_usage AS kcu
|
||||
INNER JOIN information_schema.referential_constraints AS rc
|
||||
ON (
|
||||
kcu.CONSTRAINT_NAME = rc.CONSTRAINT_NAME
|
||||
AND kcu.CONSTRAINT_SCHEMA = rc.CONSTRAINT_SCHEMA
|
||||
)
|
||||
WHERE kcu.TABLE_SCHEMA = ? AND kcu.TABLE_NAME = ? AND rc.TABLE_NAME = ?
|
||||
ORDER BY kcu.ORDINAL_POSITION ASC';
|
||||
|
||||
return [$sql, [$config['database'], $tableName, $tableName]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertForeignKeyDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$data = [
|
||||
'type' => TableSchema::CONSTRAINT_FOREIGN,
|
||||
'columns' => [$row['COLUMN_NAME']],
|
||||
'references' => [$row['REFERENCED_TABLE_NAME'], $row['REFERENCED_COLUMN_NAME']],
|
||||
'update' => $this->_convertOnClause($row['UPDATE_RULE']),
|
||||
'delete' => $this->_convertOnClause($row['DELETE_RULE']),
|
||||
];
|
||||
$name = $row['CONSTRAINT_NAME'];
|
||||
$schema->addConstraint($name, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function truncateTableSql(TableSchema $schema): array
|
||||
{
|
||||
return [sprintf('TRUNCATE TABLE `%s`', $schema->name())];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function createTableSql(TableSchema $schema, array $columns, array $constraints, array $indexes): array
|
||||
{
|
||||
$content = implode(",\n", array_merge($columns, $constraints, $indexes));
|
||||
$temporary = $schema->isTemporary() ? ' TEMPORARY ' : ' ';
|
||||
$content = sprintf("CREATE%sTABLE `%s` (\n%s\n)", $temporary, $schema->name(), $content);
|
||||
$options = $schema->getOptions();
|
||||
if (isset($options['engine'])) {
|
||||
$content .= sprintf(' ENGINE=%s', $options['engine']);
|
||||
}
|
||||
if (isset($options['charset'])) {
|
||||
$content .= sprintf(' DEFAULT CHARSET=%s', $options['charset']);
|
||||
}
|
||||
if (isset($options['collate'])) {
|
||||
$content .= sprintf(' COLLATE=%s', $options['collate']);
|
||||
}
|
||||
|
||||
return [$content];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function columnSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getColumn($name);
|
||||
|
||||
$sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name);
|
||||
if ($sql !== null) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
$out = $this->_driver->quoteIdentifier($name);
|
||||
$nativeJson = $this->_driver->supports(DriverInterface::FEATURE_JSON);
|
||||
|
||||
$typeMap = [
|
||||
TableSchema::TYPE_TINYINTEGER => ' TINYINT',
|
||||
TableSchema::TYPE_SMALLINTEGER => ' SMALLINT',
|
||||
TableSchema::TYPE_INTEGER => ' INTEGER',
|
||||
TableSchema::TYPE_BIGINTEGER => ' BIGINT',
|
||||
TableSchema::TYPE_BINARY_UUID => ' BINARY(16)',
|
||||
TableSchema::TYPE_BOOLEAN => ' BOOLEAN',
|
||||
TableSchema::TYPE_FLOAT => ' FLOAT',
|
||||
TableSchema::TYPE_DECIMAL => ' DECIMAL',
|
||||
TableSchema::TYPE_DATE => ' DATE',
|
||||
TableSchema::TYPE_TIME => ' TIME',
|
||||
TableSchema::TYPE_DATETIME => ' DATETIME',
|
||||
TableSchema::TYPE_DATETIME_FRACTIONAL => ' DATETIME',
|
||||
TableSchema::TYPE_TIMESTAMP => ' TIMESTAMP',
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL => ' TIMESTAMP',
|
||||
TableSchema::TYPE_TIMESTAMP_TIMEZONE => ' TIMESTAMP',
|
||||
TableSchema::TYPE_CHAR => ' CHAR',
|
||||
TableSchema::TYPE_UUID => ' CHAR(36)',
|
||||
TableSchema::TYPE_JSON => $nativeJson ? ' JSON' : ' LONGTEXT',
|
||||
];
|
||||
$specialMap = [
|
||||
'string' => true,
|
||||
'text' => true,
|
||||
'char' => true,
|
||||
'binary' => true,
|
||||
];
|
||||
if (isset($typeMap[$data['type']])) {
|
||||
$out .= $typeMap[$data['type']];
|
||||
}
|
||||
if (isset($specialMap[$data['type']])) {
|
||||
switch ($data['type']) {
|
||||
case TableSchema::TYPE_STRING:
|
||||
$out .= ' VARCHAR';
|
||||
if (!isset($data['length'])) {
|
||||
$data['length'] = 255;
|
||||
}
|
||||
break;
|
||||
case TableSchema::TYPE_TEXT:
|
||||
$isKnownLength = in_array($data['length'], TableSchema::$columnLengths);
|
||||
if (empty($data['length']) || !$isKnownLength) {
|
||||
$out .= ' TEXT';
|
||||
break;
|
||||
}
|
||||
|
||||
/** @var string $length */
|
||||
$length = array_search($data['length'], TableSchema::$columnLengths);
|
||||
$out .= ' ' . strtoupper($length) . 'TEXT';
|
||||
|
||||
break;
|
||||
case TableSchema::TYPE_BINARY:
|
||||
$isKnownLength = in_array($data['length'], TableSchema::$columnLengths);
|
||||
if ($isKnownLength) {
|
||||
/** @var string $length */
|
||||
$length = array_search($data['length'], TableSchema::$columnLengths);
|
||||
$out .= ' ' . strtoupper($length) . 'BLOB';
|
||||
break;
|
||||
}
|
||||
|
||||
if (empty($data['length'])) {
|
||||
$out .= ' BLOB';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($data['length'] > 2) {
|
||||
$out .= ' VARBINARY(' . $data['length'] . ')';
|
||||
} else {
|
||||
$out .= ' BINARY(' . $data['length'] . ')';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
$hasLength = [
|
||||
TableSchema::TYPE_INTEGER,
|
||||
TableSchema::TYPE_CHAR,
|
||||
TableSchema::TYPE_SMALLINTEGER,
|
||||
TableSchema::TYPE_TINYINTEGER,
|
||||
TableSchema::TYPE_STRING,
|
||||
];
|
||||
if (in_array($data['type'], $hasLength, true) && isset($data['length'])) {
|
||||
$out .= '(' . $data['length'] . ')';
|
||||
}
|
||||
|
||||
$lengthAndPrecisionTypes = [TableSchema::TYPE_FLOAT, TableSchema::TYPE_DECIMAL];
|
||||
if (in_array($data['type'], $lengthAndPrecisionTypes, true) && isset($data['length'])) {
|
||||
if (isset($data['precision'])) {
|
||||
$out .= '(' . (int)$data['length'] . ',' . (int)$data['precision'] . ')';
|
||||
} else {
|
||||
$out .= '(' . (int)$data['length'] . ')';
|
||||
}
|
||||
}
|
||||
|
||||
$precisionTypes = [TableSchema::TYPE_DATETIME_FRACTIONAL, TableSchema::TYPE_TIMESTAMP_FRACTIONAL];
|
||||
if (in_array($data['type'], $precisionTypes, true) && isset($data['precision'])) {
|
||||
$out .= '(' . (int)$data['precision'] . ')';
|
||||
}
|
||||
|
||||
$hasUnsigned = [
|
||||
TableSchema::TYPE_TINYINTEGER,
|
||||
TableSchema::TYPE_SMALLINTEGER,
|
||||
TableSchema::TYPE_INTEGER,
|
||||
TableSchema::TYPE_BIGINTEGER,
|
||||
TableSchema::TYPE_FLOAT,
|
||||
TableSchema::TYPE_DECIMAL,
|
||||
];
|
||||
if (
|
||||
in_array($data['type'], $hasUnsigned, true) &&
|
||||
isset($data['unsigned']) &&
|
||||
$data['unsigned'] === true
|
||||
) {
|
||||
$out .= ' UNSIGNED';
|
||||
}
|
||||
|
||||
$hasCollate = [
|
||||
TableSchema::TYPE_TEXT,
|
||||
TableSchema::TYPE_CHAR,
|
||||
TableSchema::TYPE_STRING,
|
||||
];
|
||||
if (in_array($data['type'], $hasCollate, true) && isset($data['collate']) && $data['collate'] !== '') {
|
||||
$out .= ' COLLATE ' . $data['collate'];
|
||||
}
|
||||
|
||||
if (isset($data['null']) && $data['null'] === false) {
|
||||
$out .= ' NOT NULL';
|
||||
}
|
||||
$addAutoIncrement = (
|
||||
$schema->getPrimaryKey() === [$name] &&
|
||||
!$schema->hasAutoincrement() &&
|
||||
!isset($data['autoIncrement'])
|
||||
);
|
||||
if (
|
||||
in_array($data['type'], [TableSchema::TYPE_INTEGER, TableSchema::TYPE_BIGINTEGER]) &&
|
||||
(
|
||||
$data['autoIncrement'] === true ||
|
||||
$addAutoIncrement
|
||||
)
|
||||
) {
|
||||
$out .= ' AUTO_INCREMENT';
|
||||
}
|
||||
|
||||
$timestampTypes = [
|
||||
TableSchema::TYPE_TIMESTAMP,
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP_TIMEZONE,
|
||||
];
|
||||
if (isset($data['null']) && $data['null'] === true && in_array($data['type'], $timestampTypes, true)) {
|
||||
$out .= ' NULL';
|
||||
unset($data['default']);
|
||||
}
|
||||
|
||||
$dateTimeTypes = [
|
||||
TableSchema::TYPE_DATETIME,
|
||||
TableSchema::TYPE_DATETIME_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP,
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP_TIMEZONE,
|
||||
];
|
||||
if (
|
||||
isset($data['default']) &&
|
||||
in_array($data['type'], $dateTimeTypes) &&
|
||||
strpos(strtolower($data['default']), 'current_timestamp') !== false
|
||||
) {
|
||||
$out .= ' DEFAULT CURRENT_TIMESTAMP';
|
||||
if (isset($data['precision'])) {
|
||||
$out .= '(' . $data['precision'] . ')';
|
||||
}
|
||||
unset($data['default']);
|
||||
}
|
||||
if (isset($data['default'])) {
|
||||
$out .= ' DEFAULT ' . $this->_driver->schemaValue($data['default']);
|
||||
unset($data['default']);
|
||||
}
|
||||
if (isset($data['comment']) && $data['comment'] !== '') {
|
||||
$out .= ' COMMENT ' . $this->_driver->schemaValue($data['comment']);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function constraintSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getConstraint($name);
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_PRIMARY) {
|
||||
$columns = array_map(
|
||||
[$this->_driver, 'quoteIdentifier'],
|
||||
$data['columns']
|
||||
);
|
||||
|
||||
return sprintf('PRIMARY KEY (%s)', implode(', ', $columns));
|
||||
}
|
||||
|
||||
$out = '';
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_UNIQUE) {
|
||||
$out = 'UNIQUE KEY ';
|
||||
}
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_FOREIGN) {
|
||||
$out = 'CONSTRAINT ';
|
||||
}
|
||||
$out .= $this->_driver->quoteIdentifier($name);
|
||||
|
||||
return $this->_keySql($out, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addConstraintSql(TableSchema $schema): array
|
||||
{
|
||||
$sqlPattern = 'ALTER TABLE %s ADD %s;';
|
||||
$sql = [];
|
||||
|
||||
foreach ($schema->constraints() as $name) {
|
||||
/** @var array $constraint */
|
||||
$constraint = $schema->getConstraint($name);
|
||||
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 FOREIGN KEY %s;';
|
||||
$sql = [];
|
||||
|
||||
foreach ($schema->constraints() as $name) {
|
||||
/** @var array $constraint */
|
||||
$constraint = $schema->getConstraint($name);
|
||||
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
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getIndex($name);
|
||||
$out = '';
|
||||
if ($data['type'] === TableSchema::INDEX_INDEX) {
|
||||
$out = 'KEY ';
|
||||
}
|
||||
if ($data['type'] === TableSchema::INDEX_FULLTEXT) {
|
||||
$out = 'FULLTEXT KEY ';
|
||||
}
|
||||
$out .= $this->_driver->quoteIdentifier($name);
|
||||
|
||||
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']
|
||||
);
|
||||
foreach ($data['columns'] as $i => $column) {
|
||||
if (isset($data['length'][$column])) {
|
||||
$columns[$i] .= sprintf('(%d)', $data['length'][$column]);
|
||||
}
|
||||
}
|
||||
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) . ')';
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
class_alias(
|
||||
'Cake\Database\Schema\MysqlSchemaDialect',
|
||||
'Cake\Database\Schema\MysqlSchema'
|
||||
);
|
||||
// phpcs:enable
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
deprecationWarning(
|
||||
'Since 4.1.0: Cake\Database\Schema\PostgresSchema is deprecated. ' .
|
||||
'Use Cake\Database\Schema\PostgresSchemaDialect instead.'
|
||||
);
|
||||
class_exists('Cake\Database\Schema\PostgresSchemaDialect');
|
||||
@@ -0,0 +1,704 @@
|
||||
<?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\Exception\DatabaseException;
|
||||
|
||||
/**
|
||||
* Schema management/reflection features for Postgres.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class PostgresSchemaDialect extends SchemaDialect
|
||||
{
|
||||
/**
|
||||
* 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 as name FROM information_schema.tables
|
||||
WHERE table_schema = ? ORDER BY name';
|
||||
$schema = empty($config['schema']) ? 'public' : $config['schema'];
|
||||
|
||||
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 as name FROM information_schema.tables
|
||||
WHERE table_schema = ? AND table_type = \'BASE TABLE\' ORDER BY name';
|
||||
$schema = empty($config['schema']) ? 'public' : $config['schema'];
|
||||
|
||||
return [$sql, [$schema]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeColumnSql(string $tableName, array $config): array
|
||||
{
|
||||
$sql = 'SELECT DISTINCT table_schema AS schema,
|
||||
column_name AS name,
|
||||
data_type AS type,
|
||||
is_nullable AS null, column_default AS default,
|
||||
character_maximum_length AS char_length,
|
||||
c.collation_name,
|
||||
d.description as comment,
|
||||
ordinal_position,
|
||||
c.datetime_precision,
|
||||
c.numeric_precision as column_precision,
|
||||
c.numeric_scale as column_scale,
|
||||
pg_get_serial_sequence(attr.attrelid::regclass::text, attr.attname) IS NOT NULL AS has_serial
|
||||
FROM information_schema.columns c
|
||||
INNER JOIN pg_catalog.pg_namespace ns ON (ns.nspname = table_schema)
|
||||
INNER JOIN pg_catalog.pg_class cl ON (cl.relnamespace = ns.oid AND cl.relname = table_name)
|
||||
LEFT JOIN pg_catalog.pg_index i ON (i.indrelid = cl.oid AND i.indkey[0] = c.ordinal_position)
|
||||
LEFT JOIN pg_catalog.pg_description d on (cl.oid = d.objoid AND d.objsubid = c.ordinal_position)
|
||||
LEFT JOIN pg_catalog.pg_attribute attr ON (cl.oid = attr.attrelid AND column_name = attr.attname)
|
||||
WHERE table_name = ? AND table_schema = ? AND table_catalog = ?
|
||||
ORDER BY ordinal_position';
|
||||
|
||||
$schema = empty($config['schema']) ? 'public' : $config['schema'];
|
||||
|
||||
return [$sql, [$tableName, $schema, $config['database']]];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a column definition to the abstract types.
|
||||
*
|
||||
* The returned type will be a type that
|
||||
* Cake\Database\TypeFactory can handle.
|
||||
*
|
||||
* @param string $column The column type + length
|
||||
* @throws \Cake\Database\Exception\DatabaseException when column cannot be parsed.
|
||||
* @return array<string, mixed> Array of column information.
|
||||
*/
|
||||
protected function _convertColumn(string $column): array
|
||||
{
|
||||
preg_match('/([a-z\s]+)(?:\(([0-9,]+)\))?/i', $column, $matches);
|
||||
if (empty($matches)) {
|
||||
throw new DatabaseException(sprintf('Unable to parse column type from "%s"', $column));
|
||||
}
|
||||
|
||||
$col = strtolower($matches[1]);
|
||||
$length = $precision = $scale = null;
|
||||
if (isset($matches[2])) {
|
||||
$length = (int)$matches[2];
|
||||
}
|
||||
|
||||
$type = $this->_applyTypeSpecificColumnConversion(
|
||||
$col,
|
||||
compact('length', 'precision', 'scale')
|
||||
);
|
||||
if ($type !== null) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
if (in_array($col, ['date', 'time', 'boolean'], true)) {
|
||||
return ['type' => $col, 'length' => null];
|
||||
}
|
||||
if (in_array($col, ['timestamptz', 'timestamp with time zone'], true)) {
|
||||
return ['type' => TableSchema::TYPE_TIMESTAMP_TIMEZONE, 'length' => null];
|
||||
}
|
||||
if (strpos($col, 'timestamp') !== false) {
|
||||
return ['type' => TableSchema::TYPE_TIMESTAMP_FRACTIONAL, 'length' => null];
|
||||
}
|
||||
if (strpos($col, 'time') !== false) {
|
||||
return ['type' => TableSchema::TYPE_TIME, 'length' => null];
|
||||
}
|
||||
if ($col === 'serial' || $col === 'integer') {
|
||||
return ['type' => TableSchema::TYPE_INTEGER, 'length' => 10];
|
||||
}
|
||||
if ($col === 'bigserial' || $col === 'bigint') {
|
||||
return ['type' => TableSchema::TYPE_BIGINTEGER, 'length' => 20];
|
||||
}
|
||||
if ($col === 'smallint') {
|
||||
return ['type' => TableSchema::TYPE_SMALLINTEGER, 'length' => 5];
|
||||
}
|
||||
if ($col === 'inet') {
|
||||
return ['type' => TableSchema::TYPE_STRING, 'length' => 39];
|
||||
}
|
||||
if ($col === 'uuid') {
|
||||
return ['type' => TableSchema::TYPE_UUID, 'length' => null];
|
||||
}
|
||||
if ($col === 'char') {
|
||||
return ['type' => TableSchema::TYPE_CHAR, 'length' => $length];
|
||||
}
|
||||
if (strpos($col, 'character') !== false) {
|
||||
return ['type' => TableSchema::TYPE_STRING, 'length' => $length];
|
||||
}
|
||||
// money is 'string' as it includes arbitrary text content
|
||||
// before the number value.
|
||||
if (strpos($col, 'money') !== false || $col === 'string') {
|
||||
return ['type' => TableSchema::TYPE_STRING, 'length' => $length];
|
||||
}
|
||||
if (strpos($col, 'text') !== false) {
|
||||
return ['type' => TableSchema::TYPE_TEXT, 'length' => null];
|
||||
}
|
||||
if ($col === 'bytea') {
|
||||
return ['type' => TableSchema::TYPE_BINARY, 'length' => null];
|
||||
}
|
||||
if ($col === 'real' || strpos($col, 'double') !== false) {
|
||||
return ['type' => TableSchema::TYPE_FLOAT, 'length' => null];
|
||||
}
|
||||
if (
|
||||
strpos($col, 'numeric') !== false ||
|
||||
strpos($col, 'decimal') !== false
|
||||
) {
|
||||
return ['type' => TableSchema::TYPE_DECIMAL, 'length' => null];
|
||||
}
|
||||
|
||||
if (strpos($col, 'json') !== false) {
|
||||
return ['type' => TableSchema::TYPE_JSON, 'length' => null];
|
||||
}
|
||||
|
||||
$length = is_numeric($length) ? $length : null;
|
||||
|
||||
return ['type' => TableSchema::TYPE_STRING, 'length' => $length];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertColumnDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$field = $this->_convertColumn($row['type']);
|
||||
|
||||
if ($field['type'] === TableSchema::TYPE_BOOLEAN) {
|
||||
if ($row['default'] === 'true') {
|
||||
$row['default'] = 1;
|
||||
}
|
||||
if ($row['default'] === 'false') {
|
||||
$row['default'] = 0;
|
||||
}
|
||||
}
|
||||
if (!empty($row['has_serial'])) {
|
||||
$field['autoIncrement'] = true;
|
||||
}
|
||||
|
||||
$field += [
|
||||
'default' => $this->_defaultValue($row['default']),
|
||||
'null' => $row['null'] === 'YES',
|
||||
'collate' => $row['collation_name'],
|
||||
'comment' => $row['comment'],
|
||||
];
|
||||
$field['length'] = $row['char_length'] ?: $field['length'];
|
||||
|
||||
if ($field['type'] === 'numeric' || $field['type'] === 'decimal') {
|
||||
$field['length'] = $row['column_precision'];
|
||||
$field['precision'] = $row['column_scale'] ?: null;
|
||||
}
|
||||
|
||||
if ($field['type'] === TableSchema::TYPE_TIMESTAMP_FRACTIONAL) {
|
||||
$field['precision'] = $row['datetime_precision'];
|
||||
if ($field['precision'] === 0) {
|
||||
$field['type'] = TableSchema::TYPE_TIMESTAMP;
|
||||
}
|
||||
}
|
||||
|
||||
if ($field['type'] === TableSchema::TYPE_TIMESTAMP_TIMEZONE) {
|
||||
$field['precision'] = $row['datetime_precision'];
|
||||
}
|
||||
|
||||
$schema->addColumn($row['name'], $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Manipulate the default value.
|
||||
*
|
||||
* Postgres includes sequence data and casting information in default values.
|
||||
* We need to remove those.
|
||||
*
|
||||
* @param string|int|null $default The default value.
|
||||
* @return string|int|null
|
||||
*/
|
||||
protected function _defaultValue($default)
|
||||
{
|
||||
if (is_numeric($default) || $default === null) {
|
||||
return $default;
|
||||
}
|
||||
// Sequences
|
||||
if (strpos($default, 'nextval') === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (strpos($default, 'NULL::') === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove quotes and postgres casts
|
||||
return preg_replace(
|
||||
"/^'(.*)'(?:::.*)$/",
|
||||
'$1',
|
||||
$default
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeIndexSql(string $tableName, array $config): array
|
||||
{
|
||||
$sql = 'SELECT
|
||||
c2.relname,
|
||||
a.attname,
|
||||
i.indisprimary,
|
||||
i.indisunique
|
||||
FROM pg_catalog.pg_namespace n
|
||||
INNER JOIN pg_catalog.pg_class c ON (n.oid = c.relnamespace)
|
||||
INNER JOIN pg_catalog.pg_index i ON (c.oid = i.indrelid)
|
||||
INNER JOIN pg_catalog.pg_class c2 ON (c2.oid = i.indexrelid)
|
||||
INNER JOIN pg_catalog.pg_attribute a ON (a.attrelid = c.oid AND i.indrelid::regclass = a.attrelid::regclass)
|
||||
WHERE n.nspname = ?
|
||||
AND a.attnum = ANY(i.indkey)
|
||||
AND c.relname = ?
|
||||
ORDER BY i.indisprimary DESC, i.indisunique DESC, c.relname, a.attnum';
|
||||
|
||||
$schema = 'public';
|
||||
if (!empty($config['schema'])) {
|
||||
$schema = $config['schema'];
|
||||
}
|
||||
|
||||
return [$sql, [$schema, $tableName]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertIndexDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$type = TableSchema::INDEX_INDEX;
|
||||
$name = $row['relname'];
|
||||
if ($row['indisprimary']) {
|
||||
$name = $type = TableSchema::CONSTRAINT_PRIMARY;
|
||||
}
|
||||
if ($row['indisunique'] && $type === TableSchema::INDEX_INDEX) {
|
||||
$type = TableSchema::CONSTRAINT_UNIQUE;
|
||||
}
|
||||
if ($type === TableSchema::CONSTRAINT_PRIMARY || $type === TableSchema::CONSTRAINT_UNIQUE) {
|
||||
$this->_convertConstraint($schema, $name, $type, $row);
|
||||
|
||||
return;
|
||||
}
|
||||
$index = $schema->getIndex($name);
|
||||
if (!$index) {
|
||||
$index = [
|
||||
'type' => $type,
|
||||
'columns' => [],
|
||||
];
|
||||
}
|
||||
$index['columns'][] = $row['attname'];
|
||||
$schema->addIndex($name, $index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add/update a constraint into the schema object.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table to update.
|
||||
* @param string $name The index name.
|
||||
* @param string $type The index type.
|
||||
* @param array $row The metadata record to update with.
|
||||
* @return void
|
||||
*/
|
||||
protected function _convertConstraint(TableSchema $schema, string $name, string $type, array $row): void
|
||||
{
|
||||
$constraint = $schema->getConstraint($name);
|
||||
if (!$constraint) {
|
||||
$constraint = [
|
||||
'type' => $type,
|
||||
'columns' => [],
|
||||
];
|
||||
}
|
||||
$constraint['columns'][] = $row['attname'];
|
||||
$schema->addConstraint($name, $constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeForeignKeySql(string $tableName, array $config): array
|
||||
{
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
$sql = 'SELECT
|
||||
c.conname AS name,
|
||||
c.contype AS type,
|
||||
a.attname AS column_name,
|
||||
c.confmatchtype AS match_type,
|
||||
c.confupdtype AS on_update,
|
||||
c.confdeltype AS on_delete,
|
||||
c.confrelid::regclass AS references_table,
|
||||
ab.attname AS references_field
|
||||
FROM pg_catalog.pg_namespace n
|
||||
INNER JOIN pg_catalog.pg_class cl ON (n.oid = cl.relnamespace)
|
||||
INNER JOIN pg_catalog.pg_constraint c ON (n.oid = c.connamespace)
|
||||
INNER JOIN pg_catalog.pg_attribute a ON (a.attrelid = cl.oid AND c.conrelid = a.attrelid AND a.attnum = ANY(c.conkey))
|
||||
INNER JOIN pg_catalog.pg_attribute ab ON (a.attrelid = cl.oid AND c.confrelid = ab.attrelid AND ab.attnum = ANY(c.confkey))
|
||||
WHERE n.nspname = ?
|
||||
AND cl.relname = ?
|
||||
ORDER BY name, a.attnum, ab.attnum DESC';
|
||||
// phpcs:enable Generic.Files.LineLength
|
||||
|
||||
$schema = empty($config['schema']) ? 'public' : $config['schema'];
|
||||
|
||||
return [$sql, [$schema, $tableName]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertForeignKeyDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$data = [
|
||||
'type' => TableSchema::CONSTRAINT_FOREIGN,
|
||||
'columns' => $row['column_name'],
|
||||
'references' => [$row['references_table'], $row['references_field']],
|
||||
'update' => $this->_convertOnClause($row['on_update']),
|
||||
'delete' => $this->_convertOnClause($row['on_delete']),
|
||||
];
|
||||
$schema->addConstraint($row['name'], $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function _convertOnClause(string $clause): string
|
||||
{
|
||||
if ($clause === 'r') {
|
||||
return TableSchema::ACTION_RESTRICT;
|
||||
}
|
||||
if ($clause === 'a') {
|
||||
return TableSchema::ACTION_NO_ACTION;
|
||||
}
|
||||
if ($clause === 'c') {
|
||||
return TableSchema::ACTION_CASCADE;
|
||||
}
|
||||
|
||||
return TableSchema::ACTION_SET_NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function columnSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getColumn($name);
|
||||
|
||||
$sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name);
|
||||
if ($sql !== null) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
$out = $this->_driver->quoteIdentifier($name);
|
||||
$typeMap = [
|
||||
TableSchema::TYPE_TINYINTEGER => ' SMALLINT',
|
||||
TableSchema::TYPE_SMALLINTEGER => ' SMALLINT',
|
||||
TableSchema::TYPE_BINARY_UUID => ' UUID',
|
||||
TableSchema::TYPE_BOOLEAN => ' BOOLEAN',
|
||||
TableSchema::TYPE_FLOAT => ' FLOAT',
|
||||
TableSchema::TYPE_DECIMAL => ' DECIMAL',
|
||||
TableSchema::TYPE_DATE => ' DATE',
|
||||
TableSchema::TYPE_TIME => ' TIME',
|
||||
TableSchema::TYPE_DATETIME => ' TIMESTAMP',
|
||||
TableSchema::TYPE_DATETIME_FRACTIONAL => ' TIMESTAMP',
|
||||
TableSchema::TYPE_TIMESTAMP => ' TIMESTAMP',
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL => ' TIMESTAMP',
|
||||
TableSchema::TYPE_TIMESTAMP_TIMEZONE => ' TIMESTAMPTZ',
|
||||
TableSchema::TYPE_UUID => ' UUID',
|
||||
TableSchema::TYPE_CHAR => ' CHAR',
|
||||
TableSchema::TYPE_JSON => ' JSONB',
|
||||
];
|
||||
|
||||
if (isset($typeMap[$data['type']])) {
|
||||
$out .= $typeMap[$data['type']];
|
||||
}
|
||||
|
||||
if ($data['type'] === TableSchema::TYPE_INTEGER || $data['type'] === TableSchema::TYPE_BIGINTEGER) {
|
||||
$type = $data['type'] === TableSchema::TYPE_INTEGER ? ' INTEGER' : ' BIGINT';
|
||||
if ($schema->getPrimaryKey() === [$name] || $data['autoIncrement'] === true) {
|
||||
$type = $data['type'] === TableSchema::TYPE_INTEGER ? ' SERIAL' : ' BIGSERIAL';
|
||||
unset($data['null'], $data['default']);
|
||||
}
|
||||
$out .= $type;
|
||||
}
|
||||
|
||||
if ($data['type'] === TableSchema::TYPE_TEXT && $data['length'] !== TableSchema::LENGTH_TINY) {
|
||||
$out .= ' TEXT';
|
||||
}
|
||||
if ($data['type'] === TableSchema::TYPE_BINARY) {
|
||||
$out .= ' BYTEA';
|
||||
}
|
||||
|
||||
if ($data['type'] === TableSchema::TYPE_CHAR) {
|
||||
$out .= '(' . $data['length'] . ')';
|
||||
}
|
||||
|
||||
if (
|
||||
$data['type'] === TableSchema::TYPE_STRING ||
|
||||
(
|
||||
$data['type'] === TableSchema::TYPE_TEXT &&
|
||||
$data['length'] === TableSchema::LENGTH_TINY
|
||||
)
|
||||
) {
|
||||
$out .= ' VARCHAR';
|
||||
if (isset($data['length']) && $data['length'] !== '') {
|
||||
$out .= '(' . $data['length'] . ')';
|
||||
}
|
||||
}
|
||||
|
||||
$hasCollate = [TableSchema::TYPE_TEXT, TableSchema::TYPE_STRING, TableSchema::TYPE_CHAR];
|
||||
if (in_array($data['type'], $hasCollate, true) && isset($data['collate']) && $data['collate'] !== '') {
|
||||
$out .= ' COLLATE "' . $data['collate'] . '"';
|
||||
}
|
||||
|
||||
$hasPrecision = [
|
||||
TableSchema::TYPE_FLOAT,
|
||||
TableSchema::TYPE_DATETIME,
|
||||
TableSchema::TYPE_DATETIME_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP,
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP_TIMEZONE,
|
||||
];
|
||||
if (in_array($data['type'], $hasPrecision) && isset($data['precision'])) {
|
||||
$out .= '(' . $data['precision'] . ')';
|
||||
}
|
||||
|
||||
if (
|
||||
$data['type'] === TableSchema::TYPE_DECIMAL &&
|
||||
(
|
||||
isset($data['length']) ||
|
||||
isset($data['precision'])
|
||||
)
|
||||
) {
|
||||
$out .= '(' . $data['length'] . ',' . (int)$data['precision'] . ')';
|
||||
}
|
||||
|
||||
if (isset($data['null']) && $data['null'] === false) {
|
||||
$out .= ' NOT NULL';
|
||||
}
|
||||
|
||||
$datetimeTypes = [
|
||||
TableSchema::TYPE_DATETIME,
|
||||
TableSchema::TYPE_DATETIME_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP,
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP_TIMEZONE,
|
||||
];
|
||||
if (
|
||||
isset($data['default']) &&
|
||||
in_array($data['type'], $datetimeTypes) &&
|
||||
strtolower($data['default']) === 'current_timestamp'
|
||||
) {
|
||||
$out .= ' DEFAULT CURRENT_TIMESTAMP';
|
||||
} elseif (isset($data['default'])) {
|
||||
$defaultValue = $data['default'];
|
||||
if ($data['type'] === 'boolean') {
|
||||
$defaultValue = (bool)$defaultValue;
|
||||
}
|
||||
$out .= ' DEFAULT ' . $this->_driver->schemaValue($defaultValue);
|
||||
} elseif (isset($data['null']) && $data['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) {
|
||||
/** @var array $constraint */
|
||||
$constraint = $schema->getConstraint($name);
|
||||
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) {
|
||||
/** @var array $constraint */
|
||||
$constraint = $schema->getConstraint($name);
|
||||
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
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getIndex($name);
|
||||
$columns = array_map(
|
||||
[$this->_driver, 'quoteIdentifier'],
|
||||
$data['columns']
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'CREATE INDEX %s ON %s (%s)',
|
||||
$this->_driver->quoteIdentifier($name),
|
||||
$this->_driver->quoteIdentifier($schema->name()),
|
||||
implode(', ', $columns)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function constraintSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
/** @var array<string, mixed> $data */
|
||||
$data = $schema->getConstraint($name);
|
||||
$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<string, mixed> $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 DEFERRABLE INITIALLY IMMEDIATE',
|
||||
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());
|
||||
$dbSchema = $this->_driver->schema();
|
||||
if ($dbSchema != 'public') {
|
||||
$tableName = $this->_driver->quoteIdentifier($dbSchema) . '.' . $tableName;
|
||||
}
|
||||
$temporary = $schema->isTemporary() ? ' TEMPORARY ' : ' ';
|
||||
$out = [];
|
||||
$out[] = sprintf("CREATE%sTABLE %s (\n%s\n)", $temporary, $tableName, $content);
|
||||
foreach ($indexes as $index) {
|
||||
$out[] = $index;
|
||||
}
|
||||
foreach ($schema->columns() as $column) {
|
||||
$columnData = $schema->getColumn($column);
|
||||
if (isset($columnData['comment'])) {
|
||||
$out[] = sprintf(
|
||||
'COMMENT ON COLUMN %s.%s IS %s',
|
||||
$tableName,
|
||||
$this->_driver->quoteIdentifier($column),
|
||||
$this->_driver->schemaValue($columnData['comment'])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function truncateTableSql(TableSchema $schema): array
|
||||
{
|
||||
$name = $this->_driver->quoteIdentifier($schema->name());
|
||||
|
||||
return [
|
||||
sprintf('TRUNCATE %s RESTART IDENTITY CASCADE', $name),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the SQL to drop a table.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema Table instance
|
||||
* @return array SQL statements to drop a table.
|
||||
*/
|
||||
public function dropTableSql(TableSchema $schema): array
|
||||
{
|
||||
$sql = sprintf(
|
||||
'DROP TABLE %s CASCADE',
|
||||
$this->_driver->quoteIdentifier($schema->name())
|
||||
);
|
||||
|
||||
return [$sql];
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
class_alias(
|
||||
'Cake\Database\Schema\PostgresSchemaDialect',
|
||||
'Cake\Database\Schema\PostgresSchema'
|
||||
);
|
||||
// phpcs:enable
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
<?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\DriverInterface;
|
||||
use Cake\Database\Type\ColumnSchemaAwareInterface;
|
||||
use Cake\Database\TypeFactory;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Base class for schema implementations.
|
||||
*
|
||||
* This class contains methods that are common across
|
||||
* the various SQL dialects.
|
||||
*
|
||||
* @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\DriverInterface
|
||||
*/
|
||||
protected $_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\DriverInterface $driver The driver to use.
|
||||
*/
|
||||
public function __construct(DriverInterface $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($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::getMap($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|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::getMap($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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
class_alias(
|
||||
'Cake\Database\Schema\SchemaDialect',
|
||||
'Cake\Database\Schema\BaseSchema'
|
||||
);
|
||||
// phpcs:enable
|
||||
@@ -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;
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
deprecationWarning(
|
||||
'Since 4.1.0: Cake\Database\Schema\SqliteSchema is deprecated. ' .
|
||||
'Use Cake\Database\Schema\SqliteSchemaDialect instead.'
|
||||
);
|
||||
class_exists('Cake\Database\Schema\SqliteSchemaDialect');
|
||||
@@ -0,0 +1,649 @@
|
||||
<?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\Exception\DatabaseException;
|
||||
|
||||
/**
|
||||
* Schema management/reflection features for Sqlite
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class SqliteSchemaDialect extends SchemaDialect
|
||||
{
|
||||
/**
|
||||
* Array containing the foreign keys constraints names
|
||||
* Necessary for composite foreign keys to be handled
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $_constraintsIdMap = [];
|
||||
|
||||
/**
|
||||
* Whether there is any table in this connection to SQLite containing sequences.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_hasSequences;
|
||||
|
||||
/**
|
||||
* Convert a column definition to the abstract types.
|
||||
*
|
||||
* The returned type will be a type that
|
||||
* Cake\Database\TypeFactory can handle.
|
||||
*
|
||||
* @param string $column The column type + length
|
||||
* @throws \Cake\Database\Exception\DatabaseException when unable to parse column type
|
||||
* @return array<string, mixed> Array of column information.
|
||||
*/
|
||||
protected function _convertColumn(string $column): array
|
||||
{
|
||||
if ($column === '') {
|
||||
return ['type' => TableSchema::TYPE_TEXT, 'length' => null];
|
||||
}
|
||||
|
||||
preg_match('/(unsigned)?\s*([a-z]+)(?:\(([0-9,]+)\))?/i', $column, $matches);
|
||||
if (empty($matches)) {
|
||||
throw new DatabaseException(sprintf('Unable to parse column type from "%s"', $column));
|
||||
}
|
||||
|
||||
$unsigned = false;
|
||||
if (strtolower($matches[1]) === 'unsigned') {
|
||||
$unsigned = true;
|
||||
}
|
||||
|
||||
$col = strtolower($matches[2]);
|
||||
$length = $precision = $scale = null;
|
||||
if (isset($matches[3])) {
|
||||
$length = $matches[3];
|
||||
if (strpos($length, ',') !== false) {
|
||||
[$length, $precision] = explode(',', $length);
|
||||
}
|
||||
$length = (int)$length;
|
||||
$precision = (int)$precision;
|
||||
}
|
||||
|
||||
$type = $this->_applyTypeSpecificColumnConversion(
|
||||
$col,
|
||||
compact('length', 'precision', 'scale')
|
||||
);
|
||||
if ($type !== null) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
if ($col === 'bigint') {
|
||||
return ['type' => TableSchema::TYPE_BIGINTEGER, 'length' => $length, 'unsigned' => $unsigned];
|
||||
}
|
||||
if ($col === 'smallint') {
|
||||
return ['type' => TableSchema::TYPE_SMALLINTEGER, 'length' => $length, 'unsigned' => $unsigned];
|
||||
}
|
||||
if ($col === 'tinyint') {
|
||||
return ['type' => TableSchema::TYPE_TINYINTEGER, 'length' => $length, 'unsigned' => $unsigned];
|
||||
}
|
||||
if (strpos($col, 'int') !== false) {
|
||||
return ['type' => TableSchema::TYPE_INTEGER, 'length' => $length, 'unsigned' => $unsigned];
|
||||
}
|
||||
if (strpos($col, 'decimal') !== false) {
|
||||
return [
|
||||
'type' => TableSchema::TYPE_DECIMAL,
|
||||
'length' => $length,
|
||||
'precision' => $precision,
|
||||
'unsigned' => $unsigned,
|
||||
];
|
||||
}
|
||||
if (in_array($col, ['float', 'real', 'double'])) {
|
||||
return [
|
||||
'type' => TableSchema::TYPE_FLOAT,
|
||||
'length' => $length,
|
||||
'precision' => $precision,
|
||||
'unsigned' => $unsigned,
|
||||
];
|
||||
}
|
||||
|
||||
if (strpos($col, 'boolean') !== false) {
|
||||
return ['type' => TableSchema::TYPE_BOOLEAN, 'length' => null];
|
||||
}
|
||||
|
||||
if (($col === 'char' && $length === 36) || $col === 'uuid') {
|
||||
return ['type' => TableSchema::TYPE_UUID, 'length' => null];
|
||||
}
|
||||
if ($col === 'char') {
|
||||
return ['type' => TableSchema::TYPE_CHAR, 'length' => $length];
|
||||
}
|
||||
if (strpos($col, 'char') !== false) {
|
||||
return ['type' => TableSchema::TYPE_STRING, 'length' => $length];
|
||||
}
|
||||
|
||||
if ($col === 'binary' && $length === 16) {
|
||||
return ['type' => TableSchema::TYPE_BINARY_UUID, 'length' => null];
|
||||
}
|
||||
if (in_array($col, ['blob', 'clob', 'binary', 'varbinary'])) {
|
||||
return ['type' => TableSchema::TYPE_BINARY, 'length' => $length];
|
||||
}
|
||||
|
||||
$datetimeTypes = [
|
||||
'date',
|
||||
'time',
|
||||
'timestamp',
|
||||
'timestampfractional',
|
||||
'timestamptimezone',
|
||||
'datetime',
|
||||
'datetimefractional',
|
||||
];
|
||||
if (in_array($col, $datetimeTypes)) {
|
||||
return ['type' => $col, 'length' => null];
|
||||
}
|
||||
|
||||
return ['type' => TableSchema::TYPE_TEXT, 'length' => null];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return [
|
||||
'SELECT name FROM sqlite_master ' .
|
||||
'WHERE (type="table" OR type="view") ' .
|
||||
'AND name != "sqlite_sequence" ORDER BY name',
|
||||
[],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return [
|
||||
'SELECT name FROM sqlite_master WHERE type="table" ' .
|
||||
'AND name != "sqlite_sequence" ORDER BY name',
|
||||
[],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeColumnSql(string $tableName, array $config): array
|
||||
{
|
||||
$sql = sprintf(
|
||||
'PRAGMA table_info(%s)',
|
||||
$this->_driver->quoteIdentifier($tableName)
|
||||
);
|
||||
|
||||
return [$sql, []];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertColumnDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$field = $this->_convertColumn($row['type']);
|
||||
$field += [
|
||||
'null' => !$row['notnull'],
|
||||
'default' => $this->_defaultValue($row['dflt_value']),
|
||||
];
|
||||
$primary = $schema->getConstraint('primary');
|
||||
|
||||
if ($row['pk'] && empty($primary)) {
|
||||
$field['null'] = false;
|
||||
$field['autoIncrement'] = true;
|
||||
}
|
||||
|
||||
// SQLite does not support autoincrement on composite keys.
|
||||
if ($row['pk'] && !empty($primary)) {
|
||||
$existingColumn = $primary['columns'][0];
|
||||
/** @psalm-suppress PossiblyNullOperand */
|
||||
$schema->addColumn($existingColumn, ['autoIncrement' => null] + $schema->getColumn($existingColumn));
|
||||
}
|
||||
|
||||
$schema->addColumn($row['name'], $field);
|
||||
if ($row['pk']) {
|
||||
$constraint = (array)$schema->getConstraint('primary') + [
|
||||
'type' => TableSchema::CONSTRAINT_PRIMARY,
|
||||
'columns' => [],
|
||||
];
|
||||
$constraint['columns'] = array_merge($constraint['columns'], [$row['name']]);
|
||||
$schema->addConstraint('primary', $constraint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manipulate the default value.
|
||||
*
|
||||
* Sqlite includes quotes and bared NULLs in default values.
|
||||
* We need to remove those.
|
||||
*
|
||||
* @param string|int|null $default The default value.
|
||||
* @return string|int|null
|
||||
*/
|
||||
protected function _defaultValue($default)
|
||||
{
|
||||
if ($default === 'NULL' || $default === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Remove quotes
|
||||
if (is_string($default) && preg_match("/^'(.*)'$/", $default, $matches)) {
|
||||
return str_replace("''", "'", $matches[1]);
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeIndexSql(string $tableName, array $config): array
|
||||
{
|
||||
$sql = sprintf(
|
||||
'PRAGMA index_list(%s)',
|
||||
$this->_driver->quoteIdentifier($tableName)
|
||||
);
|
||||
|
||||
return [$sql, []];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Since SQLite does not have a way to get metadata about all indexes at once,
|
||||
* additional queries are done here. Sqlite constraint names are not
|
||||
* stable, and the names for constraints will not match those used to create
|
||||
* the table. This is a limitation in Sqlite's metadata features.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
public function convertIndexDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$sql = sprintf(
|
||||
'PRAGMA index_info(%s)',
|
||||
$this->_driver->quoteIdentifier($row['name'])
|
||||
);
|
||||
$statement = $this->_driver->prepare($sql);
|
||||
$statement->execute();
|
||||
$columns = [];
|
||||
/** @psalm-suppress PossiblyFalseIterator */
|
||||
foreach ($statement->fetchAll('assoc') as $column) {
|
||||
$columns[] = $column['name'];
|
||||
}
|
||||
$statement->closeCursor();
|
||||
if ($row['unique']) {
|
||||
$schema->addConstraint($row['name'], [
|
||||
'type' => TableSchema::CONSTRAINT_UNIQUE,
|
||||
'columns' => $columns,
|
||||
]);
|
||||
} else {
|
||||
$schema->addIndex($row['name'], [
|
||||
'type' => TableSchema::INDEX_INDEX,
|
||||
'columns' => $columns,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeForeignKeySql(string $tableName, array $config): array
|
||||
{
|
||||
$sql = sprintf('PRAGMA foreign_key_list(%s)', $this->_driver->quoteIdentifier($tableName));
|
||||
|
||||
return [$sql, []];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertForeignKeyDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$name = $row['from'] . '_fk';
|
||||
|
||||
$update = $row['on_update'] ?? '';
|
||||
$delete = $row['on_delete'] ?? '';
|
||||
$data = [
|
||||
'type' => TableSchema::CONSTRAINT_FOREIGN,
|
||||
'columns' => [$row['from']],
|
||||
'references' => [$row['table'], $row['to']],
|
||||
'update' => $this->_convertOnClause($update),
|
||||
'delete' => $this->_convertOnClause($delete),
|
||||
];
|
||||
|
||||
if (isset($this->_constraintsIdMap[$schema->name()][$row['id']])) {
|
||||
$name = $this->_constraintsIdMap[$schema->name()][$row['id']];
|
||||
} else {
|
||||
$this->_constraintsIdMap[$schema->name()][$row['id']] = $name;
|
||||
}
|
||||
|
||||
$schema->addConstraint($name, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @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.
|
||||
* @throws \Cake\Database\Exception\DatabaseException when the column type is unknown
|
||||
*/
|
||||
public function columnSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getColumn($name);
|
||||
|
||||
$sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name);
|
||||
if ($sql !== null) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
$typeMap = [
|
||||
TableSchema::TYPE_BINARY_UUID => ' BINARY(16)',
|
||||
TableSchema::TYPE_UUID => ' CHAR(36)',
|
||||
TableSchema::TYPE_CHAR => ' CHAR',
|
||||
TableSchema::TYPE_TINYINTEGER => ' TINYINT',
|
||||
TableSchema::TYPE_SMALLINTEGER => ' SMALLINT',
|
||||
TableSchema::TYPE_INTEGER => ' INTEGER',
|
||||
TableSchema::TYPE_BIGINTEGER => ' BIGINT',
|
||||
TableSchema::TYPE_BOOLEAN => ' BOOLEAN',
|
||||
TableSchema::TYPE_FLOAT => ' FLOAT',
|
||||
TableSchema::TYPE_DECIMAL => ' DECIMAL',
|
||||
TableSchema::TYPE_DATE => ' DATE',
|
||||
TableSchema::TYPE_TIME => ' TIME',
|
||||
TableSchema::TYPE_DATETIME => ' DATETIME',
|
||||
TableSchema::TYPE_DATETIME_FRACTIONAL => ' DATETIMEFRACTIONAL',
|
||||
TableSchema::TYPE_TIMESTAMP => ' TIMESTAMP',
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL => ' TIMESTAMPFRACTIONAL',
|
||||
TableSchema::TYPE_TIMESTAMP_TIMEZONE => ' TIMESTAMPTIMEZONE',
|
||||
TableSchema::TYPE_JSON => ' TEXT',
|
||||
];
|
||||
|
||||
$out = $this->_driver->quoteIdentifier($name);
|
||||
$hasUnsigned = [
|
||||
TableSchema::TYPE_TINYINTEGER,
|
||||
TableSchema::TYPE_SMALLINTEGER,
|
||||
TableSchema::TYPE_INTEGER,
|
||||
TableSchema::TYPE_BIGINTEGER,
|
||||
TableSchema::TYPE_FLOAT,
|
||||
TableSchema::TYPE_DECIMAL,
|
||||
];
|
||||
|
||||
if (
|
||||
in_array($data['type'], $hasUnsigned, true) &&
|
||||
isset($data['unsigned']) &&
|
||||
$data['unsigned'] === true
|
||||
) {
|
||||
if ($data['type'] !== TableSchema::TYPE_INTEGER || $schema->getPrimaryKey() !== [$name]) {
|
||||
$out .= ' UNSIGNED';
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($typeMap[$data['type']])) {
|
||||
$out .= $typeMap[$data['type']];
|
||||
}
|
||||
|
||||
if ($data['type'] === TableSchema::TYPE_TEXT && $data['length'] !== TableSchema::LENGTH_TINY) {
|
||||
$out .= ' TEXT';
|
||||
}
|
||||
|
||||
if ($data['type'] === TableSchema::TYPE_CHAR) {
|
||||
$out .= '(' . $data['length'] . ')';
|
||||
}
|
||||
|
||||
if (
|
||||
$data['type'] === TableSchema::TYPE_STRING ||
|
||||
(
|
||||
$data['type'] === TableSchema::TYPE_TEXT &&
|
||||
$data['length'] === TableSchema::LENGTH_TINY
|
||||
)
|
||||
) {
|
||||
$out .= ' VARCHAR';
|
||||
|
||||
if (isset($data['length'])) {
|
||||
$out .= '(' . $data['length'] . ')';
|
||||
}
|
||||
}
|
||||
|
||||
if ($data['type'] === TableSchema::TYPE_BINARY) {
|
||||
if (isset($data['length'])) {
|
||||
$out .= ' BLOB(' . $data['length'] . ')';
|
||||
} else {
|
||||
$out .= ' BLOB';
|
||||
}
|
||||
}
|
||||
|
||||
$integerTypes = [
|
||||
TableSchema::TYPE_TINYINTEGER,
|
||||
TableSchema::TYPE_SMALLINTEGER,
|
||||
TableSchema::TYPE_INTEGER,
|
||||
];
|
||||
if (
|
||||
in_array($data['type'], $integerTypes, true) &&
|
||||
isset($data['length']) &&
|
||||
$schema->getPrimaryKey() !== [$name]
|
||||
) {
|
||||
$out .= '(' . (int)$data['length'] . ')';
|
||||
}
|
||||
|
||||
$hasPrecision = [TableSchema::TYPE_FLOAT, TableSchema::TYPE_DECIMAL];
|
||||
if (
|
||||
in_array($data['type'], $hasPrecision, true) &&
|
||||
(
|
||||
isset($data['length']) ||
|
||||
isset($data['precision'])
|
||||
)
|
||||
) {
|
||||
$out .= '(' . (int)$data['length'] . ',' . (int)$data['precision'] . ')';
|
||||
}
|
||||
|
||||
if (isset($data['null']) && $data['null'] === false) {
|
||||
$out .= ' NOT NULL';
|
||||
}
|
||||
|
||||
if ($data['type'] === TableSchema::TYPE_INTEGER && $schema->getPrimaryKey() === [$name]) {
|
||||
$out .= ' PRIMARY KEY AUTOINCREMENT';
|
||||
}
|
||||
|
||||
$timestampTypes = [
|
||||
TableSchema::TYPE_DATETIME,
|
||||
TableSchema::TYPE_DATETIME_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP,
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP_TIMEZONE,
|
||||
];
|
||||
if (isset($data['null']) && $data['null'] === true && in_array($data['type'], $timestampTypes, true)) {
|
||||
$out .= ' DEFAULT NULL';
|
||||
}
|
||||
if (isset($data['default'])) {
|
||||
$out .= ' DEFAULT ' . $this->_driver->schemaValue($data['default']);
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Note integer primary keys will return ''. This is intentional as Sqlite requires
|
||||
* that integer primary keys be defined in the column definition.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public function constraintSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getConstraint($name);
|
||||
/** @psalm-suppress PossiblyNullArrayAccess */
|
||||
if (
|
||||
$data['type'] === TableSchema::CONSTRAINT_PRIMARY &&
|
||||
count($data['columns']) === 1 &&
|
||||
$schema->getColumn($data['columns'][0])['type'] === TableSchema::TYPE_INTEGER
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
$clause = '';
|
||||
$type = '';
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_PRIMARY) {
|
||||
$type = 'PRIMARY KEY';
|
||||
}
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_UNIQUE) {
|
||||
$type = 'UNIQUE';
|
||||
}
|
||||
if ($data['type'] === TableSchema::CONSTRAINT_FOREIGN) {
|
||||
$type = 'FOREIGN KEY';
|
||||
|
||||
$clause = sprintf(
|
||||
' REFERENCES %s (%s) ON UPDATE %s ON DELETE %s',
|
||||
$this->_driver->quoteIdentifier($data['references'][0]),
|
||||
$this->_convertConstraintColumns($data['references'][1]),
|
||||
$this->_foreignOnClause($data['update']),
|
||||
$this->_foreignOnClause($data['delete'])
|
||||
);
|
||||
}
|
||||
$columns = array_map(
|
||||
[$this->_driver, 'quoteIdentifier'],
|
||||
$data['columns']
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'CONSTRAINT %s %s (%s)%s',
|
||||
$this->_driver->quoteIdentifier($name),
|
||||
$type,
|
||||
implode(', ', $columns),
|
||||
$clause
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* SQLite can not properly handle adding a constraint to an existing table.
|
||||
* This method is no-op
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table instance the foreign key constraints are.
|
||||
* @return array SQL fragment.
|
||||
*/
|
||||
public function addConstraintSql(TableSchema $schema): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* SQLite can not properly handle dropping a constraint to an existing table.
|
||||
* This method is no-op
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchema $schema The table instance the foreign key constraints are.
|
||||
* @return array SQL fragment.
|
||||
*/
|
||||
public function dropConstraintSql(TableSchema $schema): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function indexSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getIndex($name);
|
||||
$columns = array_map(
|
||||
[$this->_driver, 'quoteIdentifier'],
|
||||
$data['columns']
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'CREATE INDEX %s ON %s (%s)',
|
||||
$this->_driver->quoteIdentifier($name),
|
||||
$this->_driver->quoteIdentifier($schema->name()),
|
||||
implode(', ', $columns)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function createTableSql(TableSchema $schema, array $columns, array $constraints, array $indexes): array
|
||||
{
|
||||
$lines = array_merge($columns, $constraints);
|
||||
$content = implode(",\n", array_filter($lines));
|
||||
$temporary = $schema->isTemporary() ? ' TEMPORARY ' : ' ';
|
||||
$table = sprintf("CREATE%sTABLE \"%s\" (\n%s\n)", $temporary, $schema->name(), $content);
|
||||
$out = [$table];
|
||||
foreach ($indexes as $index) {
|
||||
$out[] = $index;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function truncateTableSql(TableSchema $schema): array
|
||||
{
|
||||
$name = $schema->name();
|
||||
$sql = [];
|
||||
if ($this->hasSequences()) {
|
||||
$sql[] = sprintf('DELETE FROM sqlite_sequence WHERE name="%s"', $name);
|
||||
}
|
||||
|
||||
$sql[] = sprintf('DELETE FROM "%s"', $name);
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether there is any table in this connection to SQLite containing
|
||||
* sequences
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSequences(): bool
|
||||
{
|
||||
$result = $this->_driver->prepare(
|
||||
'SELECT 1 FROM sqlite_master WHERE name = "sqlite_sequence"'
|
||||
);
|
||||
$result->execute();
|
||||
$this->_hasSequences = (bool)$result->rowCount();
|
||||
$result->closeCursor();
|
||||
|
||||
return $this->_hasSequences;
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
class_alias(
|
||||
'Cake\Database\Schema\SqliteSchemaDialect',
|
||||
'Cake\Database\Schema\SqliteSchema'
|
||||
);
|
||||
// phpcs:enable
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
deprecationWarning(
|
||||
'Since 4.1.0: Cake\Database\Schema\SqlserverSchema is deprecated. ' .
|
||||
'Use Cake\Database\Schema\SqlServerSchemaDialect instead.'
|
||||
);
|
||||
class_exists('Cake\Database\Schema\SqlServerSchemaDialect');
|
||||
@@ -0,0 +1,707 @@
|
||||
<?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 = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema'];
|
||||
|
||||
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 = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema'];
|
||||
|
||||
return [$sql, [$schema]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function describeColumnSql(string $tableName, array $config): array
|
||||
{
|
||||
$sql = '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]
|
||||
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]
|
||||
WHERE T.[name] = ? AND S.[name] = ?
|
||||
ORDER BY column_id';
|
||||
|
||||
$schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema'];
|
||||
|
||||
return [$sql, [$tableName, $schema]];
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' => TableSchema::TYPE_DATETIME, 'length' => null];
|
||||
}
|
||||
if (strpos($col, 'datetime') !== false) {
|
||||
$typeName = TableSchema::TYPE_DATETIME;
|
||||
if ($scale > 0) {
|
||||
$typeName = TableSchema::TYPE_DATETIME_FRACTIONAL;
|
||||
}
|
||||
|
||||
return ['type' => $typeName, 'length' => null, 'precision' => $scale];
|
||||
}
|
||||
|
||||
if ($col === 'char') {
|
||||
return ['type' => TableSchema::TYPE_CHAR, 'length' => $length];
|
||||
}
|
||||
|
||||
if ($col === 'tinyint') {
|
||||
return ['type' => TableSchema::TYPE_TINYINTEGER, 'length' => $precision ?: 3];
|
||||
}
|
||||
if ($col === 'smallint') {
|
||||
return ['type' => TableSchema::TYPE_SMALLINTEGER, 'length' => $precision ?: 5];
|
||||
}
|
||||
if ($col === 'int' || $col === 'integer') {
|
||||
return ['type' => TableSchema::TYPE_INTEGER, 'length' => $precision ?: 10];
|
||||
}
|
||||
if ($col === 'bigint') {
|
||||
return ['type' => TableSchema::TYPE_BIGINTEGER, 'length' => $precision ?: 20];
|
||||
}
|
||||
if ($col === 'bit') {
|
||||
return ['type' => TableSchema::TYPE_BOOLEAN, 'length' => null];
|
||||
}
|
||||
if (
|
||||
strpos($col, 'numeric') !== false ||
|
||||
strpos($col, 'money') !== false ||
|
||||
strpos($col, 'decimal') !== false
|
||||
) {
|
||||
return ['type' => TableSchema::TYPE_DECIMAL, 'length' => $precision, 'precision' => $scale];
|
||||
}
|
||||
|
||||
if ($col === 'real' || $col === 'float') {
|
||||
return ['type' => TableSchema::TYPE_FLOAT, 'length' => null];
|
||||
}
|
||||
// SqlServer schema reflection returns double length for unicode
|
||||
// columns because internally it uses UTF16/UCS2
|
||||
if ($col === 'nvarchar' || $col === 'nchar' || $col === 'ntext') {
|
||||
$length /= 2;
|
||||
}
|
||||
if (strpos($col, 'varchar') !== false && $length < 0) {
|
||||
return ['type' => TableSchema::TYPE_TEXT, 'length' => null];
|
||||
}
|
||||
|
||||
if (strpos($col, 'varchar') !== false) {
|
||||
return ['type' => TableSchema::TYPE_STRING, 'length' => $length ?: 255];
|
||||
}
|
||||
|
||||
if (strpos($col, 'char') !== false) {
|
||||
return ['type' => TableSchema::TYPE_CHAR, 'length' => $length];
|
||||
}
|
||||
|
||||
if (strpos($col, 'text') !== false) {
|
||||
return ['type' => TableSchema::TYPE_TEXT, 'length' => null];
|
||||
}
|
||||
|
||||
if ($col === 'image' || strpos($col, 'binary') !== false) {
|
||||
// -1 is the value for MAX which we treat as a 'long' binary
|
||||
if ($length == -1) {
|
||||
$length = TableSchema::LENGTH_LONG;
|
||||
}
|
||||
|
||||
return ['type' => TableSchema::TYPE_BINARY, 'length' => $length];
|
||||
}
|
||||
|
||||
if ($col === 'uniqueidentifier') {
|
||||
return ['type' => TableSchema::TYPE_UUID];
|
||||
}
|
||||
|
||||
return ['type' => TableSchema::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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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($type, $default)
|
||||
{
|
||||
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 === TableSchema::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 = "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]
|
||||
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]";
|
||||
|
||||
$schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema'];
|
||||
|
||||
return [$sql, [$tableName, $schema]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function convertIndexDescription(TableSchema $schema, array $row): void
|
||||
{
|
||||
$type = TableSchema::INDEX_INDEX;
|
||||
$name = $row['index_name'];
|
||||
if ($row['is_primary_key']) {
|
||||
$name = $type = TableSchema::CONSTRAINT_PRIMARY;
|
||||
}
|
||||
if ($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 (!empty($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 describeForeignKeySql(string $tableName, array $config): array
|
||||
{
|
||||
// phpcs:disable Generic.Files.LineLength
|
||||
$sql = '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
|
||||
|
||||
$schema = empty($config['schema']) ? static::DEFAULT_SCHEMA_NAME : $config['schema'];
|
||||
|
||||
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
|
||||
*/
|
||||
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
|
||||
{
|
||||
switch ($clause) {
|
||||
case 'NO_ACTION':
|
||||
return TableSchema::ACTION_NO_ACTION;
|
||||
case 'CASCADE':
|
||||
return TableSchema::ACTION_CASCADE;
|
||||
case 'SET_NULL':
|
||||
return TableSchema::ACTION_SET_NULL;
|
||||
case 'SET_DEFAULT':
|
||||
return TableSchema::ACTION_SET_DEFAULT;
|
||||
}
|
||||
|
||||
return TableSchema::ACTION_SET_NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function columnSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getColumn($name);
|
||||
|
||||
$sql = $this->_getTypeSpecificColumnSql($data['type'], $schema, $name);
|
||||
if ($sql !== null) {
|
||||
return $sql;
|
||||
}
|
||||
|
||||
$out = $this->_driver->quoteIdentifier($name);
|
||||
$typeMap = [
|
||||
TableSchema::TYPE_TINYINTEGER => ' TINYINT',
|
||||
TableSchema::TYPE_SMALLINTEGER => ' SMALLINT',
|
||||
TableSchema::TYPE_INTEGER => ' INTEGER',
|
||||
TableSchema::TYPE_BIGINTEGER => ' BIGINT',
|
||||
TableSchema::TYPE_BINARY_UUID => ' UNIQUEIDENTIFIER',
|
||||
TableSchema::TYPE_BOOLEAN => ' BIT',
|
||||
TableSchema::TYPE_CHAR => ' NCHAR',
|
||||
TableSchema::TYPE_FLOAT => ' FLOAT',
|
||||
TableSchema::TYPE_DECIMAL => ' DECIMAL',
|
||||
TableSchema::TYPE_DATE => ' DATE',
|
||||
TableSchema::TYPE_TIME => ' TIME',
|
||||
TableSchema::TYPE_DATETIME => ' DATETIME2',
|
||||
TableSchema::TYPE_DATETIME_FRACTIONAL => ' DATETIME2',
|
||||
TableSchema::TYPE_TIMESTAMP => ' DATETIME2',
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL => ' DATETIME2',
|
||||
TableSchema::TYPE_TIMESTAMP_TIMEZONE => ' DATETIME2',
|
||||
TableSchema::TYPE_UUID => ' UNIQUEIDENTIFIER',
|
||||
TableSchema::TYPE_JSON => ' NVARCHAR(MAX)',
|
||||
];
|
||||
|
||||
if (isset($typeMap[$data['type']])) {
|
||||
$out .= $typeMap[$data['type']];
|
||||
}
|
||||
|
||||
if ($data['type'] === TableSchema::TYPE_INTEGER || $data['type'] === TableSchema::TYPE_BIGINTEGER) {
|
||||
if ($schema->getPrimaryKey() === [$name] || $data['autoIncrement'] === true) {
|
||||
unset($data['null'], $data['default']);
|
||||
$out .= ' IDENTITY(1, 1)';
|
||||
}
|
||||
}
|
||||
|
||||
if ($data['type'] === TableSchema::TYPE_TEXT && $data['length'] !== TableSchema::LENGTH_TINY) {
|
||||
$out .= ' NVARCHAR(MAX)';
|
||||
}
|
||||
|
||||
if ($data['type'] === TableSchema::TYPE_CHAR) {
|
||||
$out .= '(' . $data['length'] . ')';
|
||||
}
|
||||
|
||||
if ($data['type'] === TableSchema::TYPE_BINARY) {
|
||||
if (
|
||||
!isset($data['length'])
|
||||
|| in_array($data['length'], [TableSchema::LENGTH_MEDIUM, TableSchema::LENGTH_LONG], true)
|
||||
) {
|
||||
$data['length'] = 'MAX';
|
||||
}
|
||||
|
||||
if ($data['length'] === 1) {
|
||||
$out .= ' BINARY(1)';
|
||||
} else {
|
||||
$out .= ' VARBINARY';
|
||||
|
||||
$out .= sprintf('(%s)', $data['length']);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
$data['type'] === TableSchema::TYPE_STRING ||
|
||||
(
|
||||
$data['type'] === TableSchema::TYPE_TEXT &&
|
||||
$data['length'] === TableSchema::LENGTH_TINY
|
||||
)
|
||||
) {
|
||||
$type = ' NVARCHAR';
|
||||
$length = $data['length'] ?? TableSchema::LENGTH_TINY;
|
||||
$out .= sprintf('%s(%d)', $type, $length);
|
||||
}
|
||||
|
||||
$hasCollate = [TableSchema::TYPE_TEXT, TableSchema::TYPE_STRING, TableSchema::TYPE_CHAR];
|
||||
if (in_array($data['type'], $hasCollate, true) && isset($data['collate']) && $data['collate'] !== '') {
|
||||
$out .= ' COLLATE ' . $data['collate'];
|
||||
}
|
||||
|
||||
$precisionTypes = [
|
||||
TableSchema::TYPE_FLOAT,
|
||||
TableSchema::TYPE_DATETIME,
|
||||
TableSchema::TYPE_DATETIME_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP,
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
|
||||
];
|
||||
if (in_array($data['type'], $precisionTypes, true) && isset($data['precision'])) {
|
||||
$out .= '(' . (int)$data['precision'] . ')';
|
||||
}
|
||||
|
||||
if (
|
||||
$data['type'] === TableSchema::TYPE_DECIMAL &&
|
||||
(
|
||||
isset($data['length']) ||
|
||||
isset($data['precision'])
|
||||
)
|
||||
) {
|
||||
$out .= '(' . (int)$data['length'] . ',' . (int)$data['precision'] . ')';
|
||||
}
|
||||
|
||||
if (isset($data['null']) && $data['null'] === false) {
|
||||
$out .= ' NOT NULL';
|
||||
}
|
||||
|
||||
$dateTimeTypes = [
|
||||
TableSchema::TYPE_DATETIME,
|
||||
TableSchema::TYPE_DATETIME_FRACTIONAL,
|
||||
TableSchema::TYPE_TIMESTAMP,
|
||||
TableSchema::TYPE_TIMESTAMP_FRACTIONAL,
|
||||
];
|
||||
$dateTimeDefaults = [
|
||||
'current_timestamp',
|
||||
'getdate()',
|
||||
'getutcdate()',
|
||||
'sysdatetime()',
|
||||
'sysutcdatetime()',
|
||||
'sysdatetimeoffset()',
|
||||
];
|
||||
if (
|
||||
isset($data['default']) &&
|
||||
in_array($data['type'], $dateTimeTypes, true) &&
|
||||
in_array(strtolower($data['default']), $dateTimeDefaults, true)
|
||||
) {
|
||||
$out .= ' DEFAULT ' . strtoupper($data['default']);
|
||||
} elseif (isset($data['default'])) {
|
||||
$default = is_bool($data['default'])
|
||||
? (int)$data['default']
|
||||
: $this->_driver->schemaValue($data['default']);
|
||||
$out .= ' DEFAULT ' . $default;
|
||||
} elseif (isset($data['null']) && $data['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) {
|
||||
/** @var array $constraint */
|
||||
$constraint = $schema->getConstraint($name);
|
||||
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) {
|
||||
/** @var array $constraint */
|
||||
$constraint = $schema->getConstraint($name);
|
||||
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
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getIndex($name);
|
||||
$columns = array_map(
|
||||
[$this->_driver, 'quoteIdentifier'],
|
||||
$data['columns']
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
'CREATE INDEX %s ON %s (%s)',
|
||||
$this->_driver->quoteIdentifier($name),
|
||||
$this->_driver->quoteIdentifier($schema->name()),
|
||||
implode(', ', $columns)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function constraintSql(TableSchema $schema, string $name): string
|
||||
{
|
||||
/** @var array $data */
|
||||
$data = $schema->getConstraint($name);
|
||||
$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;
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
/** @var array $column */
|
||||
$column = $schema->getColumn($pk[0]);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// phpcs:disable
|
||||
class_alias(
|
||||
'Cake\Database\Schema\SqlserverSchemaDialect',
|
||||
'Cake\Database\Schema\SqlserverSchema'
|
||||
);
|
||||
// phpcs:enable
|
||||
+793
@@ -0,0 +1,793 @@
|
||||
<?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;
|
||||
use Cake\Database\Exception\DatabaseException;
|
||||
use Cake\Database\TypeFactory;
|
||||
use function Cake\Core\deprecationWarning;
|
||||
|
||||
/**
|
||||
* Represents a single table in a database schema.
|
||||
*
|
||||
* Can either be populated using the reflection API's
|
||||
* or by incrementally building an instance using
|
||||
* methods.
|
||||
*
|
||||
* Once created TableSchema instances can be added to
|
||||
* Schema\Collection objects. They can also be converted into SQL using the
|
||||
* createSql(), dropSql() and truncateSql() methods.
|
||||
*/
|
||||
class TableSchema implements TableSchemaInterface, SqlGeneratorInterface
|
||||
{
|
||||
/**
|
||||
* The name of the table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $_table;
|
||||
|
||||
/**
|
||||
* Columns in the table.
|
||||
*
|
||||
* @var array<string, array>
|
||||
*/
|
||||
protected $_columns = [];
|
||||
|
||||
/**
|
||||
* A map with columns to types
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $_typeMap = [];
|
||||
|
||||
/**
|
||||
* Indexes in the table.
|
||||
*
|
||||
* @var array<string, array>
|
||||
*/
|
||||
protected $_indexes = [];
|
||||
|
||||
/**
|
||||
* Constraints in the table.
|
||||
*
|
||||
* @var array<string, array<string, mixed>>
|
||||
*/
|
||||
protected $_constraints = [];
|
||||
|
||||
/**
|
||||
* Options for the table.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $_options = [];
|
||||
|
||||
/**
|
||||
* Whether the table is temporary
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $_temporary = false;
|
||||
|
||||
/**
|
||||
* Column length when using a `tiny` column type
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const LENGTH_TINY = 255;
|
||||
|
||||
/**
|
||||
* Column length when using a `medium` column type
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const LENGTH_MEDIUM = 16777215;
|
||||
|
||||
/**
|
||||
* Column length when using a `long` column type
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public const LENGTH_LONG = 4294967295;
|
||||
|
||||
/**
|
||||
* Valid column length that can be used with text type columns
|
||||
*
|
||||
* @var array<string, int>
|
||||
*/
|
||||
public static $columnLengths = [
|
||||
'tiny' => self::LENGTH_TINY,
|
||||
'medium' => self::LENGTH_MEDIUM,
|
||||
'long' => self::LENGTH_LONG,
|
||||
];
|
||||
|
||||
/**
|
||||
* The valid keys that can be used in a column
|
||||
* definition.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected static $_columnKeys = [
|
||||
'type' => null,
|
||||
'baseType' => null,
|
||||
'length' => null,
|
||||
'precision' => null,
|
||||
'null' => null,
|
||||
'default' => null,
|
||||
'comment' => null,
|
||||
];
|
||||
|
||||
/**
|
||||
* Additional type specific properties.
|
||||
*
|
||||
* @var array<string, array<string, mixed>>
|
||||
*/
|
||||
protected static $_columnExtras = [
|
||||
'string' => [
|
||||
'collate' => null,
|
||||
],
|
||||
'char' => [
|
||||
'collate' => null,
|
||||
],
|
||||
'text' => [
|
||||
'collate' => null,
|
||||
],
|
||||
'tinyinteger' => [
|
||||
'unsigned' => null,
|
||||
],
|
||||
'smallinteger' => [
|
||||
'unsigned' => null,
|
||||
],
|
||||
'integer' => [
|
||||
'unsigned' => null,
|
||||
'autoIncrement' => null,
|
||||
],
|
||||
'biginteger' => [
|
||||
'unsigned' => null,
|
||||
'autoIncrement' => null,
|
||||
],
|
||||
'decimal' => [
|
||||
'unsigned' => null,
|
||||
],
|
||||
'float' => [
|
||||
'unsigned' => null,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* The valid keys that can be used in an index
|
||||
* definition.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected static $_indexKeys = [
|
||||
'type' => null,
|
||||
'columns' => [],
|
||||
'length' => [],
|
||||
'references' => [],
|
||||
'update' => 'restrict',
|
||||
'delete' => 'restrict',
|
||||
];
|
||||
|
||||
/**
|
||||
* Names of the valid index types.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected static $_validIndexTypes = [
|
||||
self::INDEX_INDEX,
|
||||
self::INDEX_FULLTEXT,
|
||||
];
|
||||
|
||||
/**
|
||||
* Names of the valid constraint types.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected static $_validConstraintTypes = [
|
||||
self::CONSTRAINT_PRIMARY,
|
||||
self::CONSTRAINT_UNIQUE,
|
||||
self::CONSTRAINT_FOREIGN,
|
||||
];
|
||||
|
||||
/**
|
||||
* Names of the valid foreign key actions.
|
||||
*
|
||||
* @var array<string>
|
||||
*/
|
||||
protected static $_validForeignKeyActions = [
|
||||
self::ACTION_CASCADE,
|
||||
self::ACTION_SET_NULL,
|
||||
self::ACTION_SET_DEFAULT,
|
||||
self::ACTION_NO_ACTION,
|
||||
self::ACTION_RESTRICT,
|
||||
];
|
||||
|
||||
/**
|
||||
* Primary constraint type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const CONSTRAINT_PRIMARY = 'primary';
|
||||
|
||||
/**
|
||||
* Unique constraint type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const CONSTRAINT_UNIQUE = 'unique';
|
||||
|
||||
/**
|
||||
* Foreign constraint type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const CONSTRAINT_FOREIGN = 'foreign';
|
||||
|
||||
/**
|
||||
* Index - index type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const INDEX_INDEX = 'index';
|
||||
|
||||
/**
|
||||
* Fulltext index type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const INDEX_FULLTEXT = 'fulltext';
|
||||
|
||||
/**
|
||||
* Foreign key cascade action
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ACTION_CASCADE = 'cascade';
|
||||
|
||||
/**
|
||||
* Foreign key set null action
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ACTION_SET_NULL = 'setNull';
|
||||
|
||||
/**
|
||||
* Foreign key no action
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ACTION_NO_ACTION = 'noAction';
|
||||
|
||||
/**
|
||||
* Foreign key restrict action
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ACTION_RESTRICT = 'restrict';
|
||||
|
||||
/**
|
||||
* Foreign key restrict default
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const ACTION_SET_DEFAULT = 'setDefault';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $table The table name.
|
||||
* @param array<string, array|string> $columns The list of columns for the schema.
|
||||
*/
|
||||
public function __construct(string $table, array $columns = [])
|
||||
{
|
||||
$this->_table = $table;
|
||||
foreach ($columns as $field => $definition) {
|
||||
$this->addColumn($field, $definition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return $this->_table;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addColumn(string $name, $attrs)
|
||||
{
|
||||
if (is_string($attrs)) {
|
||||
$attrs = ['type' => $attrs];
|
||||
}
|
||||
$valid = static::$_columnKeys;
|
||||
if (isset(static::$_columnExtras[$attrs['type']])) {
|
||||
$valid += static::$_columnExtras[$attrs['type']];
|
||||
}
|
||||
$attrs = array_intersect_key($attrs, $valid);
|
||||
$this->_columns[$name] = $attrs + $valid;
|
||||
$this->_typeMap[$name] = $this->_columns[$name]['type'];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function removeColumn(string $name)
|
||||
{
|
||||
unset($this->_columns[$name], $this->_typeMap[$name]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function columns(): array
|
||||
{
|
||||
return array_keys($this->_columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getColumn(string $name): ?array
|
||||
{
|
||||
if (!isset($this->_columns[$name])) {
|
||||
return null;
|
||||
}
|
||||
$column = $this->_columns[$name];
|
||||
unset($column['baseType']);
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getColumnType(string $name): ?string
|
||||
{
|
||||
if (!isset($this->_columns[$name])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->_columns[$name]['type'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setColumnType(string $name, string $type)
|
||||
{
|
||||
if (!isset($this->_columns[$name])) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->_columns[$name]['type'] = $type;
|
||||
$this->_typeMap[$name] = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function hasColumn(string $name): bool
|
||||
{
|
||||
return isset($this->_columns[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function baseColumnType(string $column): ?string
|
||||
{
|
||||
if (isset($this->_columns[$column]['baseType'])) {
|
||||
return $this->_columns[$column]['baseType'];
|
||||
}
|
||||
|
||||
$type = $this->getColumnType($column);
|
||||
|
||||
if ($type === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (TypeFactory::getMap($type)) {
|
||||
$type = TypeFactory::build($type)->getBaseType();
|
||||
}
|
||||
|
||||
return $this->_columns[$column]['baseType'] = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function typeMap(): array
|
||||
{
|
||||
return $this->_typeMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isNullable(string $name): bool
|
||||
{
|
||||
if (!isset($this->_columns[$name])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->_columns[$name]['null'] === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function defaultValues(): array
|
||||
{
|
||||
$defaults = [];
|
||||
foreach ($this->_columns as $name => $data) {
|
||||
if (!array_key_exists('default', $data)) {
|
||||
continue;
|
||||
}
|
||||
if ($data['default'] === null && $data['null'] !== true) {
|
||||
continue;
|
||||
}
|
||||
$defaults[$name] = $data['default'];
|
||||
}
|
||||
|
||||
return $defaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addIndex(string $name, $attrs)
|
||||
{
|
||||
if (is_string($attrs)) {
|
||||
$attrs = ['type' => $attrs];
|
||||
}
|
||||
$attrs = array_intersect_key($attrs, static::$_indexKeys);
|
||||
$attrs += static::$_indexKeys;
|
||||
unset($attrs['references'], $attrs['update'], $attrs['delete']);
|
||||
|
||||
if (!in_array($attrs['type'], static::$_validIndexTypes, true)) {
|
||||
throw new DatabaseException(sprintf(
|
||||
'Invalid index type "%s" in index "%s" in table "%s".',
|
||||
$attrs['type'],
|
||||
$name,
|
||||
$this->_table
|
||||
));
|
||||
}
|
||||
$attrs['columns'] = (array)$attrs['columns'];
|
||||
foreach ($attrs['columns'] as $field) {
|
||||
if (empty($this->_columns[$field])) {
|
||||
$msg = sprintf(
|
||||
'Columns used in index "%s" in table "%s" must be added to the Table schema first. ' .
|
||||
'The column "%s" was not found.',
|
||||
$name,
|
||||
$this->_table,
|
||||
$field
|
||||
);
|
||||
throw new DatabaseException($msg);
|
||||
}
|
||||
}
|
||||
$this->_indexes[$name] = $attrs;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function indexes(): array
|
||||
{
|
||||
return array_keys($this->_indexes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getIndex(string $name): ?array
|
||||
{
|
||||
if (!isset($this->_indexes[$name])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->_indexes[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the column(s) used for the primary key.
|
||||
*
|
||||
* @return array Column name(s) for the primary key. An
|
||||
* empty list will be returned when the table has no primary key.
|
||||
* @deprecated 4.0.0 Renamed to {@link getPrimaryKey()}.
|
||||
*/
|
||||
public function primaryKey(): array
|
||||
{
|
||||
deprecationWarning('`TableSchema::primaryKey()` is deprecated. Use `TableSchema::getPrimaryKey()`.');
|
||||
|
||||
return $this->getPrimarykey();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getPrimaryKey(): array
|
||||
{
|
||||
foreach ($this->_constraints as $data) {
|
||||
if ($data['type'] === static::CONSTRAINT_PRIMARY) {
|
||||
return $data['columns'];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addConstraint(string $name, $attrs)
|
||||
{
|
||||
if (is_string($attrs)) {
|
||||
$attrs = ['type' => $attrs];
|
||||
}
|
||||
$attrs = array_intersect_key($attrs, static::$_indexKeys);
|
||||
$attrs += static::$_indexKeys;
|
||||
if (!in_array($attrs['type'], static::$_validConstraintTypes, true)) {
|
||||
throw new DatabaseException(sprintf(
|
||||
'Invalid constraint type "%s" in table "%s".',
|
||||
$attrs['type'],
|
||||
$this->_table
|
||||
));
|
||||
}
|
||||
if (empty($attrs['columns'])) {
|
||||
throw new DatabaseException(sprintf(
|
||||
'Constraints in table "%s" must have at least one column.',
|
||||
$this->_table
|
||||
));
|
||||
}
|
||||
$attrs['columns'] = (array)$attrs['columns'];
|
||||
foreach ($attrs['columns'] as $field) {
|
||||
if (empty($this->_columns[$field])) {
|
||||
$msg = sprintf(
|
||||
'Columns used in constraints must be added to the Table schema first. ' .
|
||||
'The column "%s" was not found in table "%s".',
|
||||
$field,
|
||||
$this->_table
|
||||
);
|
||||
throw new DatabaseException($msg);
|
||||
}
|
||||
}
|
||||
|
||||
if ($attrs['type'] === static::CONSTRAINT_FOREIGN) {
|
||||
$attrs = $this->_checkForeignKey($attrs);
|
||||
|
||||
if (isset($this->_constraints[$name])) {
|
||||
$this->_constraints[$name]['columns'] = array_unique(array_merge(
|
||||
$this->_constraints[$name]['columns'],
|
||||
$attrs['columns']
|
||||
));
|
||||
|
||||
if (isset($this->_constraints[$name]['references'])) {
|
||||
$this->_constraints[$name]['references'][1] = array_unique(array_merge(
|
||||
(array)$this->_constraints[$name]['references'][1],
|
||||
[$attrs['references'][1]]
|
||||
));
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
} else {
|
||||
unset($attrs['references'], $attrs['update'], $attrs['delete']);
|
||||
}
|
||||
|
||||
$this->_constraints[$name] = $attrs;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function dropConstraint(string $name)
|
||||
{
|
||||
if (isset($this->_constraints[$name])) {
|
||||
unset($this->_constraints[$name]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a table has an autoIncrement column defined.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasAutoincrement(): bool
|
||||
{
|
||||
foreach ($this->_columns as $column) {
|
||||
if (isset($column['autoIncrement']) && $column['autoIncrement']) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to check/validate foreign keys.
|
||||
*
|
||||
* @param array<string, mixed> $attrs Attributes to set.
|
||||
* @return array<string, mixed>
|
||||
* @throws \Cake\Database\Exception\DatabaseException When foreign key definition is not valid.
|
||||
*/
|
||||
protected function _checkForeignKey(array $attrs): array
|
||||
{
|
||||
if (count($attrs['references']) < 2) {
|
||||
throw new DatabaseException('References must contain a table and column.');
|
||||
}
|
||||
if (!in_array($attrs['update'], static::$_validForeignKeyActions)) {
|
||||
throw new DatabaseException(sprintf(
|
||||
'Update action is invalid. Must be one of %s',
|
||||
implode(',', static::$_validForeignKeyActions)
|
||||
));
|
||||
}
|
||||
if (!in_array($attrs['delete'], static::$_validForeignKeyActions)) {
|
||||
throw new DatabaseException(sprintf(
|
||||
'Delete action is invalid. Must be one of %s',
|
||||
implode(',', static::$_validForeignKeyActions)
|
||||
));
|
||||
}
|
||||
|
||||
return $attrs;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function constraints(): array
|
||||
{
|
||||
return array_keys($this->_constraints);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getConstraint(string $name): ?array
|
||||
{
|
||||
return $this->_constraints[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setOptions(array $options)
|
||||
{
|
||||
$this->_options = $options + $this->_options;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setTemporary(bool $temporary)
|
||||
{
|
||||
$this->_temporary = $temporary;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function isTemporary(): bool
|
||||
{
|
||||
return $this->_temporary;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function createSql(Connection $connection): array
|
||||
{
|
||||
$dialect = $connection->getDriver()->schemaDialect();
|
||||
$columns = $constraints = $indexes = [];
|
||||
foreach (array_keys($this->_columns) as $name) {
|
||||
$columns[] = $dialect->columnSql($this, $name);
|
||||
}
|
||||
foreach (array_keys($this->_constraints) as $name) {
|
||||
$constraints[] = $dialect->constraintSql($this, $name);
|
||||
}
|
||||
foreach (array_keys($this->_indexes) as $name) {
|
||||
$indexes[] = $dialect->indexSql($this, $name);
|
||||
}
|
||||
|
||||
return $dialect->createTableSql($this, $columns, $constraints, $indexes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function dropSql(Connection $connection): array
|
||||
{
|
||||
$dialect = $connection->getDriver()->schemaDialect();
|
||||
|
||||
return $dialect->dropTableSql($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function truncateSql(Connection $connection): array
|
||||
{
|
||||
$dialect = $connection->getDriver()->schemaDialect();
|
||||
|
||||
return $dialect->truncateTableSql($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function addConstraintSql(Connection $connection): array
|
||||
{
|
||||
$dialect = $connection->getDriver()->schemaDialect();
|
||||
|
||||
return $dialect->addConstraintSql($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function dropConstraintSql(Connection $connection): array
|
||||
{
|
||||
$dialect = $connection->getDriver()->schemaDialect();
|
||||
|
||||
return $dialect->dropConstraintSql($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of the table schema.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function __debugInfo(): array
|
||||
{
|
||||
return [
|
||||
'table' => $this->_table,
|
||||
'columns' => $this->_columns,
|
||||
'indexes' => $this->_indexes,
|
||||
'constraints' => $this->_constraints,
|
||||
'options' => $this->_options,
|
||||
'typeMap' => $this->_typeMap,
|
||||
'temporary' => $this->_temporary,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Defines the interface for getting the schema.
|
||||
*/
|
||||
interface TableSchemaAwareInterface
|
||||
{
|
||||
/**
|
||||
* Get and set the schema for this fixture.
|
||||
*
|
||||
* @return \Cake\Database\Schema\TableSchemaInterface&\Cake\Database\Schema\SqlGeneratorInterface
|
||||
*/
|
||||
public function getTableSchema();
|
||||
|
||||
/**
|
||||
* Get and set the schema for this fixture.
|
||||
*
|
||||
* @param \Cake\Database\Schema\TableSchemaInterface&\Cake\Database\Schema\SqlGeneratorInterface $schema The table to set.
|
||||
* @return $this
|
||||
*/
|
||||
public function setTableSchema($schema);
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
<?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.
|
||||
*/
|
||||
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';
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* 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, $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 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'.
|
||||
*
|
||||
* 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, $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;
|
||||
}
|
||||
Reference in New Issue
Block a user